Compare commits

..

1 Commits

Author SHA1 Message Date
lamp 4d91a7b2c3 Fix public favorites
Show favorites tab to anyone when "Don't show list of my favorites" is unchecked.

Adapted from upstream changes by marcin mikołajczak <git@mkljczk.pl>:
https://git.pleroma.social/pleroma/pleroma-fe/-/commit/6f452d672fe740035cf1d29d03bcda0d39438753?merge_request_iid=1883
https://git.pleroma.social/pleroma/pleroma-fe/-/commit/1ceffb4e713b4b20d70121fba92d2b50f2d3cadf?merge_request_iid=1908
2024-05-06 17:55:10 -07:00
167 changed files with 6463 additions and 9738 deletions
+2
View File
@@ -0,0 +1,2 @@
build/*.js
config/*.js
+30
View File
@@ -0,0 +1,30 @@
module.exports = {
root: true,
parserOptions: {
parser: '@babel/eslint-parser',
sourceType: 'module'
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: [
'plugin:vue/recommended'
],
// required to lint *.vue files
plugins: [
'vue',
'import'
],
// add your custom rules here
rules: {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'vue/require-prop-types': 0,
'vue/no-unused-vars': 0,
'no-tabs': 0,
'vue/multi-word-component-names': 0,
'vue/no-reserved-component-names': 0
}
}
+1
View File
@@ -0,0 +1 @@
7.2.1
-1
View File
@@ -1 +0,0 @@
nodejs 20.12.2
+18 -24
View File
@@ -1,21 +1,20 @@
labels:
platform: linux/amd64
steps:
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
@@ -29,7 +28,7 @@ steps:
branch:
- develop
- stable
image: node:20
image: node:18
commands:
- yarn
- yarn build
@@ -41,18 +40,15 @@ steps:
branch:
- develop
- stable
image: node:20
environment:
SCW_ACCESS_KEY:
from_secret: SCW_ACCESS_KEY
SCW_SECRET_KEY:
from_secret: SCW_SECRET_KEY
SCW_DEFAULT_ORGANIZATION_ID:
from_secret: SCW_DEFAULT_ORGANIZATION_ID
image: node:18
secrets:
- SCW_ACCESS_KEY
- SCW_SECRET_KEY
- SCW_DEFAULT_ORGANIZATION_ID
commands:
- apt-get update && apt-get install -y rclone wget zip
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.30.0/scaleway-cli_2.30.0_linux_amd64
- mv scaleway-cli_2.30.0_linux_amd64 scaleway-cli
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
- mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
- chmod +x scaleway-cli
- ./scaleway-cli object config install type=rclone
- zip akkoma-fe.zip -r dist
@@ -67,17 +63,15 @@ steps:
- stable
environment:
CI: "true"
SCW_ACCESS_KEY:
from_secret: SCW_ACCESS_KEY
SCW_SECRET_KEY:
from_secret: SCW_SECRET_KEY
SCW_DEFAULT_ORGANIZATION_ID:
from_secret: SCW_DEFAULT_ORGANIZATION_ID
image: python:3.10-slim
secrets:
- SCW_ACCESS_KEY
- SCW_SECRET_KEY
- SCW_DEFAULT_ORGANIZATION_ID
commands:
- apt-get update && apt-get install -y rclone wget git zip
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.30.0/scaleway-cli_2.30.0_linux_amd64
- mv scaleway-cli_2.30.0_linux_amd64 scaleway-cli
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
- mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
- chmod +x scaleway-cli
- ./scaleway-cli object config install type=rclone
- cd docs
-2
View File
@@ -20,8 +20,6 @@ To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/ad
## Build Setup
Make sure you have [Node.js](https://nodejs.org/) installed. You can check `/.woodpecker.yml` for which node version the Akkoma CI currently uses.
``` bash
# install dependencies
corepack enable
+25 -25
View File
@@ -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({
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
}) + "\n",
);
});
chunkModules: false
}) + '\n')
})
+2 -8
View File
@@ -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
+15 -6
View File
@@ -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
View File
@@ -2,4 +2,5 @@ var { merge } = require('webpack-merge')
var devEnv = require('./dev.env')
module.exports = merge(devEnv, {
NODE_ENV: '"testing"'
})
-3
View File
@@ -70,9 +70,6 @@ Default post formatting option (markdown/bbcode/plaintext/etc...)
### `redirectRootNoLogin`, `redirectRootLogin`
These two settings should point to where FE should redirect visitor when they login/open up website root
### `scopeCopy`
Copy post scope (visibility) when replying to a post. Instance-default.
### `sidebarRight`
Change alignment of sidebar and panels to the right. Defaults to `false`.
+2 -3
View File
@@ -15,13 +15,12 @@ put a file that looks like this
```json
{
"myPack": "/static/stickers/myPack/"
"myPack": "/static/stickers/myPack"
}
```
This file is a mapping from name to pack directory location. It says "we have a pack called myPack, look for
it inside `/static/stickers/myPack`". You can add as many packs as you like in this manner.
Note that a single leading and a trailing slash are **required** to work correctly!
it at `/static/stickers/myPack`". You can add as many packs as you like in this manner.
## Creating the pack
-31
View File
@@ -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'
]
}
]
+1
View File
@@ -6,6 +6,7 @@
<title>Akkoma</title>
<link rel="stylesheet" href="/static/font/tiresias.css">
<link rel="stylesheet" href="/static/font/css/lato.css">
<link rel="stylesheet" href="/static/mfm.css">
<link rel="stylesheet" href="/static/custom.css">
<link rel="stylesheet" href="/static/theme-holder.css" id="theme-holder">
<!--server-generated-meta-->
+83 -81
View File
@@ -1,6 +1,6 @@
{
"name": "pleroma_fe",
"version": "3.12.0",
"version": "3.10.0",
"description": "A frontend for Akkoma instances",
"author": "Roger Braun <roger@rogerbraun.net>",
"private": true,
@@ -12,118 +12,120 @@
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"stylelint": "stylelint src/**/*.scss",
"lint": "eslint src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix src test/unit/specs test/e2e/specs"
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
},
"dependencies": {
"@babel/runtime": "7.17.8",
"@chenfengyuan/vue-qrcode": "^2.0.0",
"@chenfengyuan/vue-qrcode": "2.0.0",
"@floatingghost/pinch-zoom-element": "^1.3.1",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/vue-fontawesome": "^3.0.8",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"blurhash": "^2.0.5",
"body-scroll-lock": "^3.1.5",
"chromatism": "^3.0.0",
"click-outside-vue3": "^4.0.1",
"cropperjs": "^1.6.2",
"diff": "^5.2.0",
"escape-html": "^1.0.3",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@fortawesome/vue-fontawesome": "3.0.1",
"@vuelidate/core": "^2.0.0",
"@vuelidate/validators": "^2.0.0",
"blurhash": "^2.0.4",
"body-scroll-lock": "2.7.1",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
"cropperjs": "1.5.12",
"diff": "3.5.0",
"escape-html": "1.0.3",
"iso-639-1": "^2.1.15",
"js-cookie": "^3.0.1",
"localforage": "^1.10.0",
"localforage": "1.10.0",
"parse-link-header": "^2.0.0",
"phoenix": "^1.7.12",
"punycode.js": "^2.3.1",
"qrcode": "^1.5.3",
"querystring-es3": "^0.2.1",
"url": "^0.11.3",
"vue": "^3.4.38",
"vue-i18n": "^9.14.0",
"vue-router": "^4.4.3",
"vue-template-compiler": "^2.7.16",
"vuex": "^4.1.0"
"phoenix": "1.6.2",
"punycode.js": "2.1.0",
"qrcode": "1",
"url": "^0.11.0",
"vue": "^3.2.31",
"vue-i18n": "^9.2.2",
"vue-router": "4.0.14",
"vue-template-compiler": "2.6.11",
"vuex": "4.0.2"
},
"devDependencies": {
"@babel/core": "^7.24.6",
"@babel/core": "7.17.8",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.24.6",
"@babel/preset-env": "^7.24.6",
"@babel/register": "^7.24.6",
"@babel/plugin-transform-runtime": "7.17.0",
"@babel/preset-env": "7.16.11",
"@babel/register": "7.17.7",
"@intlify/vue-i18n-loader": "^5.0.0",
"@ungap/event-target": "^0.2.4",
"@vue/babel-helper-vue-jsx-merge-props": "^1.4.0",
"@vue/babel-plugin-jsx": "^1.2.2",
"@ungap/event-target": "0.2.3",
"@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
"@vue/babel-plugin-jsx": "1.1.1",
"@vue/compiler-sfc": "^3.1.0",
"@vue/test-utils": "^2.0.2",
"autoprefixer": "^10.4.19",
"autoprefixer": "6.7.7",
"babel-loader": "^9.1.0",
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-lodash": "3.3.4",
"chai": "^4.3.7",
"chalk": "^1.1.3",
"chromedriver": "^119.0.1",
"chalk": "1.1.3",
"chromedriver": "^107.0.3",
"connect-history-api-fallback": "^2.0.0",
"cross-spawn": "^7.0.3",
"css-loader": "^7.1.2",
"css-loader": "^6.7.2",
"custom-event-polyfill": "^1.0.7",
"eslint": "^9.3.0",
"eslint-config-standard": "^17.1.0",
"eslint": "^7.32.0",
"eslint-config-standard": "^17.0.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-plugin-import": "^2.29.1",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.2.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^9.26.0",
"eslint-webpack-plugin": "^4.2.0",
"eventsource-polyfill": "^0.9.6",
"express": "^4.19.2",
"eslint-plugin-vue": "^9.7.0",
"eventsource-polyfill": "0.9.6",
"express": "4.17.3",
"file-loader": "^6.2.0",
"function-bind": "^1.1.2",
"function-bind": "1.1.1",
"html-webpack-plugin": "^5.5.0",
"http-proxy-middleware": "^3.0.0",
"json-loader": "^0.5.7",
"karma": "^6.4.3",
"karma-coverage": "^2.2.1",
"karma-firefox-launcher": "^2.1.3",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"karma-sinon-chai": "^2.0.2",
"karma-sourcemap-loader": "^0.4.0",
"karma-spec-reporter": "^0.0.36",
"http-proxy-middleware": "0.21.0",
"inject-loader": "2.0.1",
"isparta-loader": "2.0.0",
"json-loader": "0.5.7",
"karma": "6.3.17",
"karma-coverage": "1.1.2",
"karma-firefox-launcher": "1.3.0",
"karma-mocha": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-sinon-chai": "2.0.2",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.33",
"karma-webpack": "^5.0.0",
"lodash": "^4.17.21",
"lolex": "^6.0.0",
"mini-css-extract-plugin": "^2.9.0",
"mocha": "^10.4.0",
"nightwatch": "^3.6.3",
"opn": "^6.0.0",
"lodash": "4.17.21",
"lolex": "1.6.0",
"mini-css-extract-plugin": "0.12.0",
"mocha": "3.5.3",
"nightwatch": "0.9.21",
"opn": "4.0.2",
"ora": "0.4.1",
"postcss-html": "^1.5.0",
"postcss-loader": "^8.1.1",
"postcss-loader": "3.0.0",
"postcss-sass": "^0.5.0",
"raw-loader": "^4.0.2",
"sass": "^1.77.2",
"sass-loader": "^14.2.1",
"selenium-server": "^3.141.59",
"semver": "^7.6.2",
"shelljs": "^0.8.5",
"sinon": "^18.0.0",
"sinon-chai": "^3.7.0",
"raw-loader": "0.5.1",
"sass": "^1.56.0",
"sass-loader": "^13.2.0",
"selenium-server": "2.53.1",
"semver": "5.7.1",
"shelljs": "0.8.5",
"sinon": "2.4.1",
"sinon-chai": "2.14.0",
"stylelint": "^14.15.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^29.0.0",
"stylelint-config-standard-scss": "^6.1.0",
"stylelint-rscss": "^0.4.0",
"url-loader": "^4.1.1",
"vue-loader": "^17.4.2",
"vue-style-loader": "^4.1.3",
"webpack": "^5.91.0",
"webpack-dev-middleware": "^7.2.1",
"webpack-hot-middleware": "^2.26.1",
"webpack-merge": "^5.10.0",
"workbox-webpack-plugin": "^7.1.0"
"vue-loader": "^17.0.0",
"vue-style-loader": "^4.1.2",
"webpack": "^5.75.0",
"webpack-dev-middleware": "^5.3.3",
"webpack-hot-middleware": "^2.25.1",
"webpack-merge": "^5.8.0",
"workbox-webpack-plugin": "^6.5.4"
},
"engines": {
"node": ">= 16.0.0",
+1 -5
View File
@@ -59,8 +59,7 @@ export default {
{
'-reverse': this.reverseLayout,
'-no-sticky-headers': this.noSticky,
'-has-new-post-button': this.newPostButtonShown,
'-wide-timeline': this.widenTimeline
'-has-new-post-button': this.newPostButtonShown
},
'-' + this.layoutType
]
@@ -94,9 +93,6 @@ export default {
newPostButtonShown () {
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
},
widenTimeline () {
return this.$store.getters.mergedConfig.widenTimeline
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
editingAvailable () { return this.$store.state.instance.editingAvailable },
layoutType () { return this.$store.state.interface.layoutType },
-9
View File
@@ -172,10 +172,6 @@ nav {
background-color: rgba(0, 0, 0, 0.15);
background-color: var(--underlay, rgba(0, 0, 0, 0.15));
z-index: -1000;
.-wide-timeline & {
margin:0 calc(var(--columnGap) / -2);
}
}
.app-layout {
@@ -191,17 +187,12 @@ nav {
grid-template-rows: 1fr;
box-sizing: border-box;
margin: 0 auto;
padding: 0 calc(var(--columnGap) / 2);
align-content: flex-start;
flex-wrap: wrap;
justify-content: center;
min-height: 100vh;
overflow-x: clip;
&.-wide-timeline {
--maxiColumn: minmax(var(--miniColumn), 1fr);
}
.column {
--___columnMargin: var(--columnGap);
-8
View File
@@ -173,10 +173,8 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('redirectRootNoLogin')
copyInstanceOption('redirectRootLogin')
copyInstanceOption('showInstanceSpecificPanel')
copyInstanceOption('minimalScopesMode')
copyInstanceOption('hideMutedPosts')
copyInstanceOption('collapseMessageWithSubject')
copyInstanceOption('scopeCopy')
copyInstanceOption('subjectLineBehavior')
copyInstanceOption('postContentType')
copyInstanceOption('alwaysShowSubjectInput')
@@ -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'])
}
@@ -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"
/>
+1 -22
View File
@@ -16,14 +16,9 @@
.attachment-wrapper {
flex: 1 1 auto;
min-height: 200px;
height: 200px;
position: relative;
overflow: hidden;
align-content: center;
.status-popover & {
height: 200px;
}
}
.description-container {
@@ -120,22 +115,6 @@
align-items: center;
justify-content: center;
padding-top: 0.5em;
p {
line-height: 1.5;
padding: 0 0.5em;
white-space: pre-line;
text-align: center;
max-height: 200px;
overflow-y: auto;
scrollbar-color: var(--border) #0000;
.status-popover & {
text-overflow: ellipsis;
overflow: hidden;
height: 1lh;
}
}
}
+2 -2
View File
@@ -246,8 +246,8 @@
ref="flash"
class="flash"
:src="attachment.large_thumb_url || attachment.url"
@player-opened="setFlashLoaded(true)"
@player-closed="setFlashLoaded(false)"
@playerOpened="setFlashLoaded(true)"
@playerClosed="setFlashLoaded(false)"
/>
</span>
</div>
+2 -2
View File
@@ -22,12 +22,12 @@
<script>
export default {
emits: ['update:modelValue'],
props: [
'modelValue',
'indeterminate',
'disabled'
],
emits: ['update:modelValue']
]
}
</script>
+2 -2
View File
@@ -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>
+3 -11
View File
@@ -267,11 +267,11 @@ const conversation = {
},
replies () {
let i = 1
// eslint-disable-next-line camelcase
return reduce(this.conversation, (result, { id, in_reply_to_status_id }) => {
/* eslint-disable camelcase */
const irid = in_reply_to_status_id
/* eslint-enable camelcase */
if (irid) {
result[irid] = result[irid] || []
result[irid].push({
@@ -414,14 +414,6 @@ const conversation = {
},
toggleExpanded () {
this.expanded = !this.expanded
const navHeight = document.getElementById("nav").offsetHeight
const headingHeight = document.getElementsByClassName("timeline-heading")[0].offsetHeight
document.documentElement.style.setProperty("--timeline-scroll-margin-top", `${navHeight + headingHeight}px`)
this.$nextTick(() => {
if (!this.expanded) {
this.$el.scrollIntoView({ block: 'nearest' })
}
})
},
getConversationId (statusId) {
const status = this.$store.state.statuses.allStatusesObject[statusId]
+2 -4
View File
@@ -91,7 +91,7 @@
:controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)"
@goto="setHighlight"
@toggle-expanded="toggleExpanded"
@toggleExpanded="toggleExpanded"
/>
<div
v-if="showOtherRepliesButtonBelowStatus && getReplies(status.id).length > 1"
@@ -184,7 +184,7 @@
:toggle-status-content-property="toggleStatusContentProperty"
@goto="setHighlight"
@toggle-expanded="toggleExpanded"
@toggleExpanded="toggleExpanded"
/>
</div>
</div>
@@ -278,7 +278,5 @@
&.-expanded.status-fadein {
margin: calc(var(--status-margin, $status-margin) / 2);
}
scroll-margin-block-start: var(--timeline-scroll-margin-top);
}
</style>
+2 -2
View File
@@ -44,9 +44,9 @@
/>
</router-link>
<router-link
v-if="publicTimelineVisible"
:to="{ name: 'public-timeline' }"
class="nav-icon"
v-if="publicTimelineVisible"
>
<FAIcon
fixed-width
@@ -68,9 +68,9 @@
/>
</router-link>
<router-link
v-if="federatedTimelineVisible"
:to="{ name: 'public-external-timeline' }"
class="nav-icon"
v-if="federatedTimelineVisible"
>
<FAIcon
fixed-width
@@ -9,7 +9,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.unmute') }}
<template #progress>
<template v-slot:progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
@@ -19,7 +19,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.mute') }}
<template #progress>
<template v-slot:progress>
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
@@ -2,7 +2,7 @@
<Modal
v-if="isFormVisible"
class="edit-form-modal-view"
@backdrop-clicked="closeModal"
@backdropClicked="closeModal"
>
<div class="edit-form-modal-panel panel">
<div class="panel-heading">
@@ -11,10 +11,10 @@
<PostStatusForm
class="panel-body"
v-bind="params"
:disable-polls="true"
:disable-visibility-selector="true"
:post-handler="doEditStatus"
@posted="closeModal"
:disablePolls="true"
:disableVisibilitySelector="true"
:post-handler="doEditStatus"
/>
</div>
</Modal>
-5
View File
@@ -1,5 +1,3 @@
import StillImage from '../still-image/still-image.vue'
const EMOJI_SIZE = 32 + 8
const GROUP_TITLE_HEIGHT = 24
const BUFFER_SIZE = 3 * EMOJI_SIZE
@@ -19,9 +17,6 @@ const EmojiGrid = {
resizeObserver: null
}
},
components: {
StillImage
},
mounted () {
const rect = this.$refs.container.getBoundingClientRect()
this.containerWidth = rect.width
+2 -3
View File
@@ -34,11 +34,10 @@
@click.stop.prevent="onEmoji(item.emoji)"
>
<span v-if="!item.emoji.imageUrl">{{ item.emoji.replacement }}</span>
<StillImage
<img
v-else
:src="item.emoji.imageUrl"
noStopGifs="true"
/>
>
</span>
</template>
</div>
+1 -3
View File
@@ -1,6 +1,5 @@
import Completion from '../../services/completion/completion.js'
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
import StillImage from '../still-image/still-image.vue'
import { take } from 'lodash'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
@@ -121,8 +120,7 @@ const EmojiInput = {
}
},
components: {
EmojiPicker,
StillImage
EmojiPicker
},
computed: {
padEmoji () {
+3 -8
View File
@@ -20,7 +20,6 @@
ref="picker"
show-keep-open
:class="{ hide: !showPicker }"
:visible="showPicker"
:enable-sticker-picker="enableStickerPicker"
class="emoji-picker-panel"
@emoji="insert"
@@ -44,15 +43,11 @@
:class="{ highlighted: index === highlighted }"
@click.stop.prevent="onClick($event, suggestion)"
>
<span
v-if="!suggestion.mfm"
class="image"
>
<StillImage
<span v-if="!suggestion.mfm" class="image">
<img
v-if="suggestion.img"
:src="suggestion.img"
noStopGifs="true"
/>
>
<span v-else>{{ suggestion.replacement }}</span>
</span>
<div class="label">
+3 -3
View File
@@ -1,4 +1,4 @@
const MFM_TAGS = ['bg', 'blur', 'bounce', 'center', 'fg', 'flip', 'font', 'jelly', 'jump', 'position', 'rainbow', 'rotate', 'scale', 'shake', 'sparkle', 'spin', 'tada', 'twitch', 'x2', 'x3', 'x4']
const MFM_TAGS = ['blur', 'bounce', 'flip', 'font', 'jelly', 'jump', 'rainbow', 'rotate', 'shake', 'sparkle', 'spin', 'tada', 'twitch', 'x2', 'x3', 'x4']
.map(tag => ({ displayText: tag, detailText: '$[' + tag + ' ]', replacement: '$[' + tag + ' ]', mfm: true }))
/**
@@ -122,14 +122,14 @@ export const suggestUsers = ({ dispatch, state }) => {
const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */
}).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
displayText: screen_name_ui,
detailText: name,
imageUrl: profile_image_url_original,
replacement: '@' + screen_name + ' '
}))
/* eslint-enable camelcase */
suggestions = newSuggestions || []
return suggestions
+2 -21
View File
@@ -1,7 +1,6 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue'
import EmojiGrid from '../emoji_grid/emoji_grid.vue'
import StillImage from '../still-image/still-image.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBoxOpen,
@@ -27,17 +26,12 @@ const EmojiPicker = {
required: false,
type: Boolean,
default: false
},
visible: {
required: false,
type: Boolean,
default: true
}
},
data () {
return {
keyword: '',
activeGroup: this.getDefaultGroup(),
activeGroup: 'standard',
showingStickers: false,
keepOpen: false
}
@@ -45,8 +39,7 @@ const EmojiPicker = {
components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
Checkbox,
EmojiGrid,
StillImage
EmojiGrid
},
methods: {
debouncedSearch: debounce(function (e) {
@@ -89,11 +82,6 @@ const EmojiPicker = {
return list.filter(emoji => {
return (regex.test(emoji.displayText) || (!emoji.imageUrl && emoji.replacement === this.keyword))
})
},
getDefaultGroup () {
if (!this.visible) return null
const recentEmojis = this.$store.getters.recentEmojis
return recentEmojis.length === 0 ? 'standard' : 'recent'
}
},
computed: {
@@ -160,13 +148,6 @@ const EmojiPicker = {
stickerPickerEnabled () {
return (this.$store.state.instance.stickers || []).length !== 0 && this.enableStickerPicker
}
},
watch: {
visible (val, oldVal) {
if (val && this.activeGroup === null) {
this.activeGroup = this.getDefaultGroup()
}
}
}
}
+2 -3
View File
@@ -18,11 +18,10 @@
@click.prevent="highlight(group.id)"
>
<span v-if="!group.first.imageUrl">{{ group.first.replacement }}</span>
<StillImage
<img
v-else
:src="group.first.imageUrl"
noStopGifs="true"
/>
>
</span>
<span
v-if="stickerPickerEnabled"
@@ -11,7 +11,7 @@
@click="emojiOnClick(reaction.name, $event)"
@mouseenter="fetchEmojiReactionsByIfMissing()"
>
<template
<span
v-if="reaction.url !== null"
>
<StillImage
@@ -19,15 +19,16 @@
:title="reaction.name"
:alt="reaction.name"
class="reaction-emoji"
height="2.55em"
/>
{{ reaction.count }}
</template>
<template v-else>
</span>
<span v-else>
<span class="reaction-emoji unicode-emoji">
{{ reaction.name }}
</span>
<span>{{ reaction.count }}</span>
</template>
</span>
</button>
</UserListPopover>
<a
@@ -52,26 +53,23 @@
container-type: inline-size;
}
.unicode-emoji {
font-size: 210%;
}
.emoji-reaction {
padding: 2px 0.5em;
padding: 0 0.5em;
margin-right: 0.5em;
margin-top: 0.5em;
display: flex;
align-items: end;
align-items: center;
justify-content: center;
box-sizing: border-box;
.reaction-emoji {
width: auto;
max-width: 96cqw;
height: 2.55em !important;
margin-right: 0.25em;
&.still-image {
height: 2.55em;
}
&.unicode-emoji {
display: inline-block;
font-size: 2.125em; // assuming default line height of 1.2rem and emojis that don't exceed line height
line-height: 2.55rem;
}
}
&:focus {
outline: none;
@@ -99,9 +97,9 @@
}
.button-default.picked-reaction {
&, &:hover {
box-shadow: inset 0 0 0 1px var(--accent, $fallback--link);
}
border: 1px solid var(--accent, $fallback--link);
margin-left: -1px; // offset the border, can't use inset shadows either
margin-right: calc(0.5em - 1px);
}
</style>
@@ -7,7 +7,7 @@
:bound-to="{ x: 'container' }"
remove-padding
>
<template #content="{close}">
<template v-slot:content="{close}">
<div class="dropdown-menu">
<button
v-if="canMute && !status.thread_muted"
@@ -172,7 +172,7 @@
</button>
</div>
</template>
<template #trigger>
<template v-slot:trigger>
<button class="button-unstyled popover-trigger">
<FAIcon
class="fa-scale-110 fa-old-padding"
@@ -4,7 +4,6 @@ const FeaturesPanel = {
computed: {
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
minimalScopesMode: function () { return this.$store.state.instance.minimalScopesMode },
textlimit: function () { return this.$store.state.instance.textlimit },
uploadlimit: function () { return fileSizeFormatService.fileSizeFormat(this.$store.state.instance.uploadlimit) }
}
@@ -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"
+4 -2
View File
@@ -88,8 +88,10 @@ const Gallery = {
set(this.sizes, id, { width, height })
},
rowStyle (row) {
if (!row.audio && !row.minimal && !row.grid) {
return { 'aspect-ratio': `1/${(1 / (row.items.length + 0.6))}` }
if (row.audio) {
return { 'padding-bottom': '25%' } // fixed reduced height for audio
} else if (!row.minimal && !row.grid) {
return { 'padding-bottom': `${(100 / (row.items.length + 0.6))}%` }
}
},
itemStyle (id, row) {
+3 -9
View File
@@ -31,8 +31,8 @@
:description="descriptions && descriptions[attachment.id]"
:hide-description="size === 'small' || tooManyAttachments && hidingLong"
:style="itemStyle(attachment.id, row.items)"
@set-media="onMedia"
@natural-size-load="onNaturalSizeLoad"
@setMedia="onMedia"
@naturalSizeLoad="onNaturalSizeLoad"
/>
</div>
</div>
@@ -96,15 +96,9 @@
.gallery-row {
position: relative;
height: 0;
width: 100%;
flex-grow: 1;
.Status & {
max-height: 30em;
}
&.-audio {
aspect-ratio: 4/1; // this is terrible, but it's how it was before so I'm not changing it >:(
}
&:not(:first-child) {
margin-top: 0.5em;
-1
View File
@@ -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;
+3 -4
View File
@@ -2,7 +2,7 @@
<Modal
v-if="showing"
class="media-modal-view"
@backdrop-clicked="hideIfNotSwiped"
@backdropClicked="hideIfNotSwiped"
>
<SwipeClick
v-if="type === 'image'"
@@ -24,15 +24,14 @@
:min-scale="pinchZoomMinScale"
:reset-to-min-scale-limit="pinchZoomScaleResetLimit"
>
<StillImage
<img
:class="{ loading }"
class="modal-image"
:src="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
@load="onImageLoaded"
noStopGifs="true"
/>
>
</PinchZoom>
</SwipeClick>
<VideoAttachment
+1 -1
View File
@@ -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()
})
-1
View File
@@ -18,7 +18,6 @@
<input
id="code"
v-model="code"
autocomplete="one-time-code"
class="form-control"
>
</div>
@@ -4,7 +4,7 @@
class="panel-heading"
@click="toggleHidden"
>
<h4>{{ $t('moderation.reports.report') + ' ' + account.screen_name }}</h4>
<h4>{{ $t('moderation.reports.report') + ' ' + this.account.screen_name }}</h4>
<button
v-if="isOpen"
class="button-default"
@@ -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
-3
View File
@@ -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)"
+1 -3
View File
@@ -6,7 +6,6 @@ import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import StillImage from '../still-image/still-image.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -51,8 +50,7 @@ const Notification = {
Timeago,
Status,
RichContent,
ConfirmModal,
StillImage
ConfirmModal
},
methods: {
toggleUserExpanded () {
@@ -101,8 +101,4 @@
color: $fallback--cBlue;
color: var(--cBlue, $fallback--cBlue);
}
.attachment-wrapper {
min-height: unset;
}
}
+3 -4
View File
@@ -116,13 +116,12 @@
scope="global"
keypath="notifications.reacted_with"
>
<still-image
<img
v-if="notification.emoji_url !== null"
class="notification-reaction-emoji"
:src="notification.emoji_url"
:title="notification.emoji"
:alt="notification.emoji"
/>
:name="notification.emoji"
>
<span
v-else
class="emoji-reaction-emoji"
@@ -5,7 +5,7 @@
placement="bottom"
:bound-to="{ x: 'container' }"
>
<template #content>
<template v-slot:content>
<div class="dropdown-menu">
<button
class="button-default dropdown-item"
@@ -72,7 +72,7 @@
</button>
</div>
</template>
<template #trigger>
<template v-slot:trigger>
<button class="filter-trigger-button button-unstyled">
<FAIcon icon="filter" />
</button>
@@ -14,7 +14,7 @@
:model-value="present"
:disabled="disabled"
class="opt"
@update:model-value="$emit('update:modelValue', !present ? fallback : undefined)"
@update:modelValue="$emit('update:modelValue', !present ? fallback : undefined)"
/>
<input
:id="name"
+1
View File
@@ -2,6 +2,7 @@
<pinch-zoom
class="pinch-zoom-parent"
v-bind="$attrs"
v-on="$listeners"
>
<slot />
</pinch-zoom>
+2 -10
View File
@@ -50,13 +50,6 @@ export default {
totalVotesCount () {
return this.poll.votes_count
},
totalFractionBase () {
// Due to a backend bug, we might not have any voter count info for remote polls
// in this case, fall back to count of votes even for multiple cjoice polls
// to be able to at least display _something_
const total_base = this.poll.multiple ? this.poll.voters_count : this.poll.votes_count
return total_base > 0 ? total_base : this.poll.votes_count
},
containerClass () {
return {
loading: this.loading
@@ -77,11 +70,10 @@ export default {
},
methods: {
percentageForOption (count) {
const total = this.totalFractionBase
return total === 0 ? 0 : Math.round(count / total * 100)
return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)
},
resultTitle (option) {
return `${option.votes_count}/${this.totalFractionBase} ${this.$t('polls.votes')}`
return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
},
fetchPoll () {
this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
-2
View File
@@ -24,7 +24,6 @@
<button
v-if="options.length > 2"
class="delete-option button-unstyled -hover-highlight"
type="button"
@click="deleteOption(index)"
>
<FAIcon icon="times" />
@@ -33,7 +32,6 @@
<button
v-if="options.length < maxOptions"
class="add-option faint button-unstyled -hover-highlight"
type="button"
@click="addOption"
>
<FAIcon
@@ -9,13 +9,11 @@ import StatusContent from '../status_content/status_content.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
import { reject, map, uniqBy, debounce } from 'lodash'
import { usePostLanguageOptions } from 'src/lib/post_language'
import scopeUtils from 'src/lib/scope_utils.js'
import suggestor from '../emoji_input/suggestor.js'
import { mapGetters, mapState } from 'vuex'
import Checkbox from '../checkbox/checkbox.vue'
import Select from '../select/select.vue'
import iso6391 from 'iso-639-1'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -64,13 +62,6 @@ const deleteDraft = (draftKey) => {
localStorage.setItem('drafts', JSON.stringify(draftData));
}
const interfaceToISOLanguage = (ilang) => {
const sep = ilang.indexOf("_");
return sep < 0 ?
ilang :
ilang.substr(0, sep);
}
const PostStatusForm = {
props: [
'statusId',
@@ -86,7 +77,6 @@ const PostStatusForm = {
'quoteId',
'repliedUser',
'attentions',
'copyMessageLanguage',
'copyMessageScope',
'subject',
'disableSubject',
@@ -139,13 +129,6 @@ const PostStatusForm = {
this.$refs.textarea.focus()
}
},
setup() {
const {postLanguageOptions} = usePostLanguageOptions()
return {
postLanguageOptions,
}
},
data () {
const preset = this.$route.query.message
let statusText = preset || ''
@@ -155,7 +138,7 @@ const PostStatusForm = {
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
}
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, alwaysShowSubjectInput } = this.$store.getters.mergedConfig
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, interfaceLanguage, alwaysShowSubjectInput } = this.$store.getters.mergedConfig
let statusParams = {
spoilerText: this.subject || '',
@@ -166,7 +149,7 @@ const PostStatusForm = {
poll: {},
mediaDescriptions: {},
visibility: this.suggestedVisibility(),
language: this.suggestedLanguage(),
language: interfaceLanguage,
contentType
}
@@ -181,7 +164,7 @@ const PostStatusForm = {
poll: this.statusPoll || {},
mediaDescriptions: this.statusMediaDescriptions || {},
visibility: this.statusScope || this.suggestedVisibility(),
language: this.statusLanguage || this.suggestedLanguage(),
language: this.statusLanguage || interfaceLanguage,
contentType: statusContentType
}
}
@@ -247,9 +230,6 @@ const PostStatusForm = {
userDefaultScope () {
return this.$store.state.users.currentUser.default_scope
},
showAllScopes () {
return !this.mergedConfig.minimalScopesMode
},
emojiUserSuggestor () {
return suggestor({
emoji: [
@@ -291,9 +271,6 @@ const PostStatusForm = {
isOverLengthLimit () {
return this.hasStatusLengthLimit && (this.charactersLeft < 0)
},
minimalScopesMode () {
return this.$store.state.instance.minimalScopesMode
},
alwaysShowSubject () {
return this.mergedConfig.alwaysShowSubjectInput
},
@@ -332,11 +309,13 @@ const PostStatusForm = {
...mapState({
mobileLayout: state => state.interface.mobileLayout
}),
isoLanguages () {
return iso6391.getAllCodes();
}
},
watch: {
'newStatus': {
deep: true,
flush: 'sync',
handler () {
this.statusChanged()
}
@@ -349,22 +328,17 @@ const PostStatusForm = {
this.saveDraft()
},
clearStatus () {
const config = this.$store.getters.mergedConfig
const newStatus = this.newStatus
this.newStatus = {
status: '',
spoilerText: '',
files: [],
nsfw: !!config.sensitiveByDefault,
visibility: this.suggestedVisibility(),
contentType: config.postContentType,
language: this.suggestedLanguage(),
visibility: newStatus.visibility,
contentType: newStatus.contentType,
language: newStatus.language,
poll: {},
mediaDescriptions: {}
}
const scopeselector = this.$refs.scopeselector
if (scopeselector) {
scopeselector.currentScope = this.newStatus.visibility
}
this.pollFormVisible = false
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
this.clearPollForm()
@@ -524,7 +498,7 @@ const PostStatusForm = {
addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo)
if (this.$store.getters.mergedConfig.sensitiveIfSubject && this.newStatus.spoilerText !== '' || !!this.$store.getters.mergedConfig.sensitiveByDefault) {
if (this.$store.getters.mergedConfig.sensitiveIfSubject && this.newStatus.spoilerText !== '') {
this.newStatus.nsfw = true
}
this.$emit('resize', { delayed: true })
@@ -773,19 +747,16 @@ const PostStatusForm = {
openProfileTab () {
this.$store.dispatch('openSettingsModalTab', 'profile')
},
suggestedLanguage () {
// Make sure the inherited language is actually valid
if (this.postLanguageOptions.find(o => o.value === this.copyMessageLanguage)) {
return this.copyMessageLanguage
}
const { postLanguage: defaultPostLanguage, interfaceLanguage } = this.$store.getters.mergedConfig
const postLanguage = defaultPostLanguage || interfaceToISOLanguage(interfaceLanguage)
return postLanguage
},
suggestedVisibility () {
const maxScope = this.copyMessageScope
const defaultScope = this.$store.state.users.currentUser.default_scope
return scopeUtils.negotiate(defaultScope, maxScope)
if (this.copyMessageScope) {
if (this.copyMessageScope === 'direct') {
return this.copyMessageScope
}
if (this.copyMessageScope !== 'public' && this.$store.state.users.currentUser.default_scope !== 'private') {
return this.copyMessageScope
}
}
return this.$store.state.users.currentUser.default_scope
}
}
}
@@ -18,7 +18,6 @@
>
<button
class="button-unstyled -link"
type="button"
@click="openProfileTab"
>
{{ $t('post_status.account_not_locked_warning_link') }}
@@ -119,8 +118,8 @@
/>
</div>
<EmojiInput
v-if="subjectVisible"
ref="subject-emoji-input"
v-if="subjectVisible"
v-model="newStatus.spoilerText"
enable-emoji-picker
hide-emoji-button
@@ -137,7 +136,6 @@
class="form-post-subject"
@input="onSubjectInput"
@focus="focusSubjectInput()"
@keydown.exact.enter.prevent
>
</EmojiInput>
<i18n-t
@@ -172,7 +170,7 @@
cols="1"
:disabled="posting && !optimisticPosting"
class="form-post-body"
:class="{ 'scrollable-form': !!maxHeight, '-has-subject': subjectVisible }"
:class="{ 'scrollable-form': !!maxHeight }"
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
@keydown.meta.enter="postStatus($event, newStatus)"
@keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
@@ -192,12 +190,9 @@
<div
v-if="!disableScopeSelector"
class="visibility-tray"
:class="{ 'visibility-tray-edit': isEdit }"
>
<scope-selector
ref="scopeselector"
v-if="!disableVisibilitySelector"
:show-all="showAllScopes"
:user-default="userDefaultScope"
:original-scope="copyMessageScope"
:initial-scope="newStatus.visibility"
@@ -205,9 +200,7 @@
/>
<div
class="format-selector-container">
<div
class="format-selector"
class="language-selector"
>
<Select
id="post-language"
@@ -215,17 +208,17 @@
class="form-control"
>
<option
v-for="language in postLanguageOptions"
:key="language.key"
:value="language.value"
v-for="language in isoLanguages"
:key="language"
:value="language"
>
{{ language.label }}
{{ language }}
</option>
</Select>
</div>
<div
v-if="postFormats.length > 1"
class="text-format format-selector"
class="text-format"
>
<Select
id="post-content-type"
@@ -243,7 +236,7 @@
</div>
<div
v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
class="text-format format-selector"
class="text-format"
>
<span class="only-format">
{{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
@@ -251,7 +244,6 @@
</div>
</div>
</div>
</div>
<poll-form
v-if="pollsAvailable"
ref="pollForm"
@@ -276,7 +268,6 @@
<button
class="emoji-icon button-unstyled"
:title="$t('emoji.add_emoji')"
type="button"
@click="showEmojiPicker"
>
<FAIcon icon="smile-beam" />
@@ -286,7 +277,6 @@
class="poll-icon button-unstyled"
:class="{ selected: pollFormVisible }"
:title="$t('polls.add_poll')"
type="button"
@click="togglePollForm"
>
<FAIcon icon="poll-h" />
@@ -296,7 +286,6 @@
class="spoiler-icon button-unstyled"
:class="{ selected: subjectVisible }"
:title="$t('post_status.toggle_content_warning')"
type="button"
@click="toggleSubjectVisible"
>
<FAIcon icon="eye-slash" />
@@ -471,10 +460,6 @@
align-items: baseline;
}
.visibility-tray-edit {
justify-content: right;
}
.visibility-notice.edit-warning {
> :first-child {
margin-top: 0;
@@ -485,12 +470,6 @@
}
}
.format-selector-container {
.format-selector {
display: inline-block;
}
}
.media-upload-icon, .poll-icon, .emoji-icon, .spoiler-icon {
font-size: 1.85em;
line-height: 1.1;
@@ -591,11 +570,6 @@
line-height: 1.85;
}
.form-post-subject {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.form-post-body {
// TODO: make a resizable textarea component?
box-sizing: content-box; // needed for easier computation of dynamic size
@@ -608,11 +582,6 @@
min-height: calc(var(--post-line-height) * 1em);
resize: none;
&.-has-subject {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
&.scrollable-form {
overflow-y: auto;
}
@@ -3,7 +3,7 @@
v-if="isLoggedIn && !resettingForm"
:is-open="modalActivated"
class="post-form-modal-view"
@backdrop-clicked="closeModal"
@backdropClicked="closeModal"
>
<div class="post-form-modal-panel panel">
<div class="panel-heading">
+2 -2
View File
@@ -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')"
@@ -2,7 +2,7 @@ export default {
props: [ 'user' ],
computed: {
subscribeUrl () {
// eslint-disable-next-line no-undef
const serverUrl = new URL(this.user.statusnet_profile_url)
return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus`
}
@@ -1,6 +1,4 @@
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import ScopeSelector from '../scope_selector/scope_selector.vue'
import scopeUtils from 'src/lib/scope_utils.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faRetweet } from '@fortawesome/free-solid-svg-icons'
@@ -9,14 +7,12 @@ library.add(faRetweet)
const RetweetButton = {
props: ['status', 'loggedIn', 'visibility'],
components: {
ConfirmModal,
ScopeSelector
ConfirmModal
},
data () {
return {
animated: false,
showingConfirmDialog: false,
retweetVisibility: this.$store.state.users.currentUser.default_scope
showingConfirmDialog: false
}
},
methods: {
@@ -29,7 +25,7 @@ const RetweetButton = {
},
doRetweet () {
if (!this.status.repeated) {
this.$store.dispatch('retweet', { id: this.status.id, visibility: this.retweetVisibility })
this.$store.dispatch('retweet', { id: this.status.id })
} else {
this.$store.dispatch('unretweet', { id: this.status.id })
}
@@ -44,9 +40,6 @@ const RetweetButton = {
},
hideConfirmDialog () {
this.showingConfirmDialog = false
},
changeVis (visibility) {
this.retweetVisibility = visibility
}
},
computed: {
@@ -61,15 +54,6 @@ const RetweetButton = {
},
remoteInteractionLink () {
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
},
userDefaultScope () {
return this.$store.state.users.currentUser.default_scope
},
statusScope () {
return this.status.visibility
},
initialScope () {
return scopeUtils.negotiate(this.userDefaultScope, this.status.visibility)
}
}
}
@@ -49,12 +49,6 @@
@cancelled="hideConfirmDialog"
>
{{ $t('status.repeat_confirm') }}
<scope-selector
:user-default="userDefaultScope"
:original-scope="statusScope"
:initial-scope="initialScope"
:on-scope-change="changeVis"
/>
</confirm-modal>
</teleport>
</div>
@@ -121,19 +121,6 @@ export default {
}
}
const mfmStyleFromDataAttributes = (attributes) => {
// CSS selectors can check if a data-* attribute is true, but can't use other values, so we want to add them to the style attribute
// Here we turn e.g. `{'data-mfm-some': '1deg', 'data-mfm-thing': '5s'}` to "--mfm-some: 1deg;--mfm-thing: 5s;"
// Note that we only add the value to `style` when they contain only letters, numbers, dot, or minus signs
// At the moment of writing, this should be enough for legitimate purposes and reduces the chance of injection by using special characters
// There is a special case for the `color` value, who is provided without `#`, but requires this in the `style` attribute
return Object.keys(attributes).filter(
(key) => key.startsWith('data-mfm-') && attributes[key] !== true && /^[a-zA-Z0-9.\-]*$/.test(attributes[key])
).map(
(key) => '--mfm-' + key.substr(9) + (key === 'data-mfm-color' ? ': #' : ': ') + attributes[key] + ';'
).reduce((a,v) => a+v, '')
}
// Processor to use with html_tree_converter
const processItem = (item, index, array, what) => {
// Handle text nodes - just add emoji
@@ -204,15 +191,6 @@ export default {
if (this.handleLinks && attrs?.['class']?.includes?.('h-card')) {
return ['', children.map(processItem), '']
}
let mfm_style = mfmStyleFromDataAttributes(attrs)
if (mfm_style !== '') {
return [
opener.slice(0,-1) + ' style="' + mfm_style + '">',
children.map(processItem),
closer
]
}
}
if (children !== undefined) {
+20 -10
View File
@@ -6,8 +6,6 @@ import {
faGlobe
} from '@fortawesome/free-solid-svg-icons'
import scopeUtils from 'src/lib/scope_utils.js'
library.add(
faEnvelope,
faGlobe,
@@ -15,11 +13,18 @@ library.add(
faLockOpen
)
const SCOPE_LEVELS = {
'direct': 0,
'private': 1,
'local': 2,
'unlisted': 2,
'public': 3
}
const ScopeSelector = {
props: [
'showAll',
'userDefault',
// scope of parent object
'originalScope',
'initialScope',
'onScopeChange'
@@ -34,16 +39,16 @@ const ScopeSelector = {
return !this.showPublic && !this.showUnlisted && !this.showPrivate && !this.showDirect
},
showPublic () {
return this.shouldShow('public')
return this.originalScope !== 'direct' && this.shouldShow('public')
},
showLocal () {
return this.shouldShow('local')
return this.originalScope !== 'direct' && this.shouldShow('local')
},
showUnlisted () {
return this.shouldShow('unlisted')
return this.originalScope !== 'direct' && this.shouldShow('unlisted')
},
showPrivate () {
return this.shouldShow('private')
return this.originalScope !== 'direct' && this.shouldShow('private')
},
showDirect () {
return this.shouldShow('direct')
@@ -60,10 +65,15 @@ const ScopeSelector = {
},
methods: {
shouldShow (scope) {
if (!this.originalScope)
if (!this.originalScope) {
return true
else
return scopeUtils.compare(scope, this.originalScope) <= 0
}
if (this.originalScope === 'local') {
return scope === 'direct' || scope === 'local'
}
return SCOPE_LEVELS[scope] <= SCOPE_LEVELS[this.originalScope]
},
changeVis (scope) {
this.currentScope = scope
@@ -24,7 +24,7 @@
:items="items"
:get-key="getKey"
>
<template #item="{item}">
<template v-slot:item="{item}">
<div
class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
@@ -41,7 +41,7 @@
/>
</div>
</template>
<template #empty>
<template v-slot:empty>
<slot name="empty" />
</template>
</List>
@@ -6,7 +6,7 @@
<Checkbox
:model-value="state"
:disabled="disabled"
@update:model-value="update"
@update:modelValue="update"
>
<span
v-if="!!$slots.default"
@@ -8,7 +8,7 @@
<Select
:model-value="state"
:disabled="disabled"
@update:model-value="update"
@update:modelValue="update"
>
<option
v-for="option in options"
@@ -6,14 +6,14 @@
<Popover
trigger="hover"
>
<template #trigger>
<template v-slot:trigger>
&nbsp;
<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>
&nbsp;
<FAIcon
icon="server"
:aria-label="$t('settings.setting_server_side')"
/>
</template>
<template #content>
<template v-slot:content>
<div class="serverside-tooltip">
{{ $t('settings.setting_server_side') }}
</div>
@@ -69,7 +69,7 @@ const SettingsModal = {
this.$store.dispatch('closeSettingsModal')
},
logout () {
this.$router.replace(this.$store.state.instance.redirectRootNoLogin || '/main/all')
this.$router.replace('/main/public')
this.$store.dispatch('closeSettingsModal')
this.$store.dispatch('logout')
},
@@ -108,7 +108,7 @@
<Checkbox
:model-value="!!expertLevel"
class="expertMode"
@update:model-value="expertLevel = Number($event)"
@update:modelValue="expertLevel = Number($event)"
>
{{ $t("settings.expert_mode") }}
</Checkbox>
@@ -72,7 +72,7 @@ const DataImportExportTab = {
// check is it's a local user
if (user && user.is_local) {
// append the instance address
// eslint-disable-next-line no-undef
return user.screen_name + '@' + location.hostname
}
return user.screen_name
@@ -4,7 +4,6 @@ import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
import IntegerSetting from '../helpers/integer_setting.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import { usePostLanguageOptions } from 'src/lib/post_language'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import ServerSideIndicator from '../helpers/server_side_indicator.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -18,11 +17,6 @@ library.add(
)
const GeneralTab = {
setup() {
const {postLanguageOptions} = usePostLanguageOptions()
return {postLanguageOptions}
},
data () {
return {
subjectLineOptions: ['email', 'noop', 'masto'].map(mode => ({
@@ -124,12 +118,6 @@ const GeneralTab = {
this.$store.dispatch('setOption', { name: 'translationLanguage', value: val })
}
},
postLanguage: {
get: function () { return this.$store.getters.mergedConfig.postLanguage },
set: function (val) {
this.$store.dispatch('setOption', { name: 'postLanguage', value: val })
}
},
...SharedComputedObject()
},
methods: {
@@ -44,6 +44,7 @@
<template
v-if="profilesExpanded"
>
<div
v-for="profile in settingsProfiles"
:key="profile.id"
@@ -72,24 +73,15 @@
</button>
</template>
</div>
<button
class="btn button-default"
@click="refreshProfiles()"
>
<button class="btn button-default" @click="refreshProfiles()">
{{ $t('settings.settings_profiles_refresh') }}
<FAIcon
icon="sync"
@click="refreshProfiles()"
/>
<FAIcon icon="sync" @click="refreshProfiles()" />
</button>
<h3>{{ $t('settings.settings_profile_creation') }}</h3>
<label for="settings-profile-new-name">
{{ $t('settings.settings_profile_creation_new_name_label') }}
</label>
<input
id="settings-profile-new-name"
v-model="newProfileName"
>
<input v-model="newProfileName" id="settings-profile-new-name">
<button
class="btn button-default"
@click="createSettingsProfile"
@@ -159,16 +151,6 @@
{{ $t('settings.show_page_backgrounds') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="centerAlignBio">
{{ $t('settings.center_align_bio') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="compactUserInfo">
{{ $t('settings.compact_user_info') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }}
@@ -279,11 +261,6 @@
{{ $t('settings.right_sidebar') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="widenTimeline">
{{ $t('settings.widen_timeline') }}
</BooleanSetting>
</li>
<li>
<ChoiceSetting
v-if="user"
@@ -565,18 +542,12 @@
{{ $t('settings.default_vis') }} <ServerSideIndicator :server-side="true" />
<ScopeSelector
class="scope-selector"
:show-all="true"
:user-default="serverSide_defaultScope"
:initial-scope="serverSide_defaultScope"
:on-scope-change="changeDefaultScope"
/>
</label>
</li>
<li>
<BooleanSetting path="minimalScopesMode">
{{ $t('settings.minimal_scopes_mode') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="sensitiveByDefault">
{{ $t('settings.sensitive_by_default') }}
@@ -587,14 +558,6 @@
{{ $t('settings.sensitive_if_subject') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="scopeCopy"
expert="1"
>
{{ $t('settings.scope_copy') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="alwaysShowSubjectInput"
@@ -622,23 +585,6 @@
{{ $t('settings.post_status_content_type') }}
</ChoiceSetting>
</li>
<li>
<ChoiceSetting
id="postLanguage"
path="postLanguage"
:options="postLanguageOptions"
>
{{ $t('settings.post_language') }}
</ChoiceSetting>
</li>
<li>
<BooleanSetting
path="minimalScopesMode"
expert="1"
>
{{ $t('settings.minimal_scopes_mode') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="alwaysShowNewPostButton"
@@ -85,7 +85,7 @@ const MutesAndBlocks = {
// check is it's a local user
if (user && user.is_local) {
// append the instance address
// eslint-disable-next-line no-undef
return user.screen_name + '@' + location.hostname
}
return user.screen_name
@@ -10,7 +10,7 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_block')"
>
<template #default="row">
<template v-slot="row">
<BlockCard
:user-id="row.item"
/>
@@ -21,7 +21,7 @@
:refresh="true"
:get-key="i => i"
>
<template #header="{selected}">
<template v-slot:header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -29,7 +29,7 @@
:click="() => blockUsers(selected)"
>
{{ $t('user_card.block') }}
<template #progress>
<template v-slot:progress>
{{ $t('user_card.block_progress') }}
</template>
</ProgressButton>
@@ -39,16 +39,16 @@
:click="() => unblockUsers(selected)"
>
{{ $t('user_card.unblock') }}
<template #progress>
<template v-slot:progress>
{{ $t('user_card.unblock_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template #item="{item}">
<template v-slot:item="{item}">
<BlockCard :user-id="item" />
</template>
<template #empty>
<template v-slot:empty>
{{ $t('settings.no_blocks') }}
</template>
</BlockList>
@@ -63,7 +63,7 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_mute')"
>
<template #default="row">
<template v-slot="row">
<MuteCard
:user-id="row.item"
/>
@@ -74,7 +74,7 @@
:refresh="true"
:get-key="i => i"
>
<template #header="{selected}">
<template v-slot:header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -82,7 +82,7 @@
:click="() => muteUsers(selected)"
>
{{ $t('user_card.mute') }}
<template #progress>
<template v-slot:progress>
{{ $t('user_card.mute_progress') }}
</template>
</ProgressButton>
@@ -92,16 +92,16 @@
:click="() => unmuteUsers(selected)"
>
{{ $t('user_card.unmute') }}
<template #progress>
<template v-slot:progress>
{{ $t('user_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template #item="{item}">
<template v-slot:item="{item}">
<MuteCard :user-id="item" />
</template>
<template #empty>
<template v-slot:empty>
{{ $t('settings.no_mutes') }}
</template>
</MuteList>
@@ -114,7 +114,7 @@
:query="queryKnownDomains"
:placeholder="$t('settings.type_domains_to_mute')"
>
<template #default="row">
<template v-slot="row">
<DomainMuteCard
:domain="row.item"
/>
@@ -125,7 +125,7 @@
:refresh="true"
:get-key="i => i"
>
<template #header="{selected}">
<template v-slot:header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -133,16 +133,16 @@
:click="() => unmuteDomains(selected)"
>
{{ $t('domain_mute_card.unmute') }}
<template #progress>
<template v-slot:progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template #item="{item}">
<template v-slot:item="{item}">
<DomainMuteCard :domain="item" />
</template>
<template #empty>
<template v-slot:empty>
{{ $t('settings.no_mutes') }}
</template>
</DomainMuteList>
@@ -130,7 +130,7 @@ const ProfileTab = {
note: this.newBio,
locked: this.newLocked,
// Backend notation.
/* eslint-disable camelcase */
display_name: this.newName,
fields_attributes: this.newFields.filter(el => el != null),
bot: this.bot,
@@ -138,7 +138,7 @@ const ProfileTab = {
status_ttl_days: this.expirePosts ? this.newPostTTLDays : -1,
permit_followback: this.permit_followback,
accepts_direct_messages_from: this.userAcceptsDirectMessagesFrom
/* eslint-enable camelcase */
}
if (this.emailLanguage) {
@@ -187,7 +187,7 @@ const ProfileTab = {
})
return
}
// eslint-disable-next-line no-undef
const reader = new FileReader()
reader.onload = ({ target }) => {
const img = target.result
@@ -110,9 +110,11 @@
max="730"
class="expire-posts-days"
:placeholder="$t('settings.expire_posts_input_placeholder')"
>
/>
</p>
<p>
</p>
<p />
<p>
<interface-language-switcher
:prompt-text="$t('settings.email_language')"
@@ -1,25 +1,22 @@
import { extractCommit } from 'src/services/version/version.service'
function joinURL(base, subpath) {
return URL.parse(subpath, base)?.href || "invalid base URL"
}
const pleromaFeCommitUrl = 'https://akkoma.dev/AkkomaGang/pleroma-fe/commit/'
const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commit/'
const VersionTab = {
data () {
const instance = this.$store.state.instance
return {
backendCommitUrl: instance.backendCommitUrl,
backendVersion: instance.backendVersion,
frontendCommitUrl: instance.frontendCommitUrl,
frontendVersion: instance.frontendVersion
}
},
computed: {
frontendVersionLink () {
return joinURL(this.frontendCommitUrl, this.frontendVersion)
return pleromaFeCommitUrl + this.frontendVersion
},
backendVersionLink () {
return joinURL(this.backendCommitUrl, extractCommit(this.backendVersion))
return pleromaBeCommitUrl + extractCommit(this.backendVersion)
}
}
}
-10
View File
@@ -266,16 +266,6 @@
color: $fallback--cGreen;
color: var(--cGreen, $fallback--cGreen);
}
.right-side {
display: flex;
align-items: center;
gap: 0.3em;
}
.repeat-tooltip {
flex-shrink: 0;
}
}
.repeater-avatar {
+5 -12
View File
@@ -83,7 +83,7 @@
:user="statusoid.user"
/>
<div class="right-side faint">
<div
<span
class="status-username repeater-name"
:title="retweeter"
>
@@ -100,12 +100,8 @@
v-else
:to="retweeterProfileLink"
>{{ retweeter }}</router-link>
</div>
</span>
{{ ' ' }}
<div
class="repeat-tooltip"
>
<FAIcon
icon="retweet"
class="repeat-icon"
@@ -114,7 +110,6 @@
{{ $t('timeline.repeated') }}
</div>
</div>
</div>
<div
v-if="!deleted"
@@ -373,7 +368,7 @@
:controlled-toggle-showing-long-subject="controlledToggleShowingLongSubject"
@mediaplay="addMediaPlaying($event)"
@mediapause="removeMediaPlaying($event)"
@parse-ready="setHeadTailLinks"
@parseReady="setHeadTailLinks"
/>
</div>
@@ -481,8 +476,8 @@
/>
<extra-buttons
:status="status"
@on-error="showError"
@on-success="clearError"
@onError="showError"
@onSuccess="clearError"
/>
</div>
</div>
@@ -519,7 +514,6 @@
:reply-to="status.id"
:attentions="status.attentions"
:replied-user="status.user"
:copy-message-language="status.language"
:copy-message-scope="status.visibility"
:subject="replySubject"
@posted="toggleReplying"
@@ -534,7 +528,6 @@
:quote-id="status.id"
:attentions="[status.user]"
:replied-user="status.user"
:copy-message-language="status.language"
:copy-message-scope="status.visibility"
:subject="replySubject"
@posted="toggleQuoting"
+2 -6
View File
@@ -41,8 +41,7 @@ const StatusContent = {
postLength: this.status.text.length,
parseReadyDone: false,
renderMisskeyMarkdown,
translateFrom: null,
translating: false
translateFrom: null
}
},
computed: {
@@ -136,10 +135,7 @@ const StatusContent = {
},
translateStatus () {
const translateTo = this.$store.getters.mergedConfig.translationLanguage || this.$store.state.instance.interfaceLanguage
this.translating = true
this.$store.dispatch(
'translateStatus', { id: this.status.id, language: translateTo, from: this.translateFrom }
).finally(() => { this.translating = false })
this.$store.dispatch('translateStatus', { id: this.status.id, language: translateTo, from: this.translateFrom })
}
}
}
+18 -1
View File
@@ -3,7 +3,6 @@
.StatusBody {
display: flex;
flex-direction: column;
overflow: hidden;
.translation {
border: 1px solid var(--accent, $fallback--link);
@@ -24,6 +23,24 @@
transition: 0.05s;
}
._mfm_x2_ {
.emoji {
height: 100px;
}
}
._mfm_x3_ {
.emoji {
height: 150px;
}
}
._mfm_x4_ {
.emoji {
height: 200px;
}
}
.attachments {
margin-top: 0.5em;
}
+4 -8
View File
@@ -1,7 +1,7 @@
<template>
<div
class="StatusBody"
:class="{ '-compact': compact }"
:class="{ '-compact': compact, 'mfm-disabled': !renderMisskeyMarkdown }"
>
<div class="body">
<div
@@ -54,7 +54,7 @@
:mfm="renderMisskeyMarkdown && (status.media_type === 'text/x.misskeymarkdown')"
:greentext="mergedConfig.greentext"
:attentions="status.attentions"
@parse-ready="onParseReady"
@parseReady="onParseReady"
/>
<div
v-if="status.translation"
@@ -70,7 +70,7 @@
:mfm="renderMisskeyMarkdown && (status.media_type === 'text/x.misskeymarkdown')"
:greentext="mergedConfig.greentext"
:attentions="status.attentions"
@parse-ready="onParseReady"
@parseReady="onParseReady"
/>
<div>
<label class="label">{{ $t('status.override_translation_source_language') }}</label>
@@ -89,11 +89,7 @@
</option>
</Select>
{{ ' ' }}
<button
class="btn button-default"
:disabled="translating"
@click="translateStatus"
>
<button @click="translateStatus" class="btn button-default">
{{ $t('status.translate') }}
</button>
</div>
-423
View File
@@ -1,423 +0,0 @@
/**
* "FEP-c16b: Formatting MFM functions" attributes that Akkoma supports
*/
.StatusContent:not(.mfm-disabled) {
/* The following are the non-animated MFM */
.mfm-center {
display: block;
text-align: center;
}
.mfm-flip {
display: inline-block;
transform: scaleX(-1);
}
.mfm-flip[data-mfm-v] {
transform: scaleY(-1);
}
.mfm-flip[data-mfm-v][data-mfm-h] {
transform: scale(-1, -1);
}
.mfm-font[data-mfm-serif] {
font-family: serif;
}
.mfm-font[data-mfm-monospace] {
font-family: monospace;
}
.mfm-font[data-mfm-cursive] {
font-family: cursive;
}
.mfm-font[data-mfm-fantasy] {
font-family: fantasy;
}
.mfm-font[data-mfm-emoji] {
font-family: emoji;
}
.mfm-font[data-mfm-math] {
font-family: math;
}
.mfm-blur {
filter: blur(6px);
transition: filter 0.3s;
&:hover {
filter: blur(0);
}
}
.mfm-rotate {
display: inline-block;
transform: rotate(calc(var(--mfm-deg, 90) * 1deg));
transform-origin: center center;
}
.mfm-x2 {
--mfm-zoom-size: 200%;
}
.mfm-x3 {
--mfm-zoom-size: 400%;
}
.mfm-x4 {
--mfm-zoom-size: 600%;
}
.mfm-x2,
.mfm-x3,
.mfm-x4,
.mfm-tada {
.emoji {
--emoji-size: 2em;
}
font-size: var(--mfm-zoom-size);
.mfm-x2,
.mfm-x3,
.mfm-x4,
.mfm-tada {
/* only half effective */
font-size: calc(var(--mfm-zoom-size) / 2 + 50%);
.mfm-x2,
.mfm-x3,
.mfm-x4,
.mfm-tada {
/* disabled */
font-size: 100%;
}
}
}
.mfm-position {
display: inline-block;
transform: translate(calc(var(--mfm-x, 0) * 1em), calc(var(--mfm-y, 0) * 1em));
}
.mfm-scale {
display: inline-block;
transform: scale(var(--mfm-x, 1), var(--mfm-y, 1));
}
.mfm-fg {
color: var(--mfm-color, #f00);
}
.mfm-bg {
background-color: var(--mfm-color, #0f0);
}
/* The following are the animated MFM */
/* .mfm-hover means that we should only play animation when hovering over the StatusContent
* So either StatusContent does not have this class,
* or it has the class and we are hovering over StatusContent
*/
&:not(.mfm-hover:not(:hover)) {
.mfm-jelly {
display: inline-block;
animation: mfm-rubberBand var(--mfm-speed, 1s) linear infinite both;
}
.mfm-twitch {
display: inline-block;
animation: mfm-twitch var(--mfm-speed, 0.5s) ease infinite;
}
.mfm-shake {
display: inline-block;
animation: mfm-shake var(--mfm-speed, 0.5s) ease infinite;
}
.mfm-spin {
display: inline-block;
animation: mfm-spin var(--mfm-speed, 1.5s) linear infinite;
}
.mfm-spin[data-mfm-y] {
animation-name: mfm-spinY;
}
.mfm-spin[data-mfm-x] {
animation-name: mfm-spinX;
}
.mfm-spin[data-mfm-alternate] {
animation-direction: alternate;
}
.mfm-spin[data-mfm-left] {
animation-direction: reverse;
}
.mfm-jump {
display: inline-block;
animation: mfm-jump var(--mfm-speed, 0.75s) linear infinite;
}
.mfm-bounce {
display: inline-block;
animation: mfm-bounce var(--mfm-speed, 0.75s) linear infinite;
transform-origin: center bottom;
}
.mfm-rainbow {
animation: mfm-rainbow var(--mfm-speed, 1s) linear infinite;
}
.mfm-tada {
display: inline-block;
animation: mfm-tada var(--mfm-speed, 1s) linear infinite both;
--mfm-zoom-size: 150%;
}
}
/* animation keyframes */
@keyframes mfm-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes mfm-spinX {
0% { transform: perspective(128px) rotateX(0deg); }
100% { transform: perspective(128px) rotateX(360deg); }
}
@keyframes mfm-spinY {
0% { transform: perspective(128px) rotateY(0deg); }
100% { transform: perspective(128px) rotateY(360deg); }
}
@keyframes mfm-jump {
0% { transform: translateY(0); }
25% { transform: translateY(-16px); }
50% { transform: translateY(0); }
75% { transform: translateY(-8px); }
100% { transform: translateY(0); }
}
@keyframes mfm-bounce {
0% { transform: translateY(0) scale(1, 1); }
25% { transform: translateY(-16px) scale(1, 1); }
50% { transform: translateY(0) scale(1, 1); }
75% { transform: translateY(0) scale(1.5, 0.75); }
100% { transform: translateY(0) scale(1, 1); }
}
@keyframes mfm-twitch {
0% { transform: translate(7px, -2px); }
5% { transform: translate(-3px, 1px); }
10% { transform: translate(-7px, -1px); }
15% { transform: translate(0, -1px); }
20% { transform: translate(-8px, 6px); }
25% { transform: translate(-4px, -3px); }
30% { transform: translate(-4px, -6px); }
35% { transform: translate(-8px, -8px); }
40% { transform: translate(4px, 6px); }
45% { transform: translate(-3px, 1px); }
50% { transform: translate(2px, -10px); }
55% { transform: translate(-7px, 0); }
60% { transform: translate(-2px, 4px); }
65% { transform: translate(3px, -8px); }
70% { transform: translate(6px, 7px); }
75% { transform: translate(-7px, -2px); }
80% { transform: translate(-7px, -8px); }
85% { transform: translate(9px, 3px); }
90% { transform: translate(-3px, -2px); }
95% { transform: translate(-10px, 2px); }
100% { transform: translate(-2px, -6px); }
}
@keyframes mfm-shake {
0% { transform: translate(-3px, -1px) rotate(-8deg); }
5% { transform: translate(0, -1px) rotate(-10deg); }
10% { transform: translate(1px, -3px) rotate(0deg); }
15% { transform: translate(1px, 1px) rotate(11deg); }
20% { transform: translate(-2px, 1px) rotate(1deg); }
25% { transform: translate(-1px, -2px) rotate(-2deg); }
30% { transform: translate(-1px, 2px) rotate(-3deg); }
35% { transform: translate(2px, 1px) rotate(6deg); }
40% { transform: translate(-2px, -3px) rotate(-9deg); }
45% { transform: translate(0, -1px) rotate(-12deg); }
50% { transform: translate(1px, 2px) rotate(10deg); }
55% { transform: translate(0, -3px) rotate(8deg); }
60% { transform: translate(1px, -1px) rotate(8deg); }
65% { transform: translate(0, -1px) rotate(-7deg); }
70% { transform: translate(-1px, -3px) rotate(6deg); }
75% { transform: translate(0, -2px) rotate(4deg); }
80% { transform: translate(-2px, -1px) rotate(3deg); }
85% { transform: translate(1px, -3px) rotate(-10deg); }
90% { transform: translate(1px, 0) rotate(3deg); }
95% { transform: translate(-2px, 0) rotate(-3deg); }
100% { transform: translate(2px, 1px) rotate(2deg); }
}
@keyframes mfm-rubberBand {
0% { transform: scale3d(1, 1, 1); }
30% { transform: scale3d(1.25, 0.75, 1); }
40% { transform: scale3d(0.75, 1.25, 1); }
50% { transform: scale3d(1.15, 0.85, 1); }
65% { transform: scale3d(0.95, 1.05, 1); }
75% { transform: scale3d(1.05, 0.95, 1); }
100% { transform: scale3d(1, 1, 1); }
}
@keyframes mfm-rainbow {
0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); }
100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); }
}
@keyframes mfm-tada {
0%,
100% { transform: scale3d(1, 1, 1); }
10%,
20% { transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); }
30%,
50%,
70%,
90% { transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); }
40%,
60%,
80% { transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); }
}
/**
* Legacy MFM
* This is for backwards compatibility with posts formatted on Akkoma before support for FEP-c16b
* Note that it uses the keyframes as defined above for the FEP-c16b compatible MFM representation
*/
.mfm {
display: inline-block;
}
/* The following are the legacy non-animated MFM */
._mfm_flip_[data-h][data-v] {
transform: scale(-1, -1);
}
._mfm_flip_[data-v] {
transform: scaleY(-1);
}
._mfm_flip_:not([data-v]) {
transform: scaleX(-1);
}
._mfm_x2_ {
font-size: 200%;
}
._mfm_x3_ {
font-size: 400%;
}
._mfm_x4_ {
font-size: 600%;
}
._mfm_x2_ {
.emoji {
height: 100px;
}
}
._mfm_x3_ {
.emoji {
height: 150px;
}
}
._mfm_x4_ {
.emoji {
height: 200px;
}
}
._mfm_blur_ {
filter: blur(6px);
transition: filter 0.3s;
}
._mfm_blur_:hover {
filter: blur(0);
}
._mfm_rotate_ {
transform: rotate(90deg);
transform-origin: center center;
}
/* The following are the legacy animated MFM */
/* .mfm-hover means that we should only play animation when hovering over the StatusContent
* So either StatusContent does not have this class,
* or it has the class and we are hovering over StatusContent
*/
&:not(.mfm-hover:not(:hover)) {
._mfm_tada_ {
font-size: 150%;
animation: mfm-tada 1s linear infinite both;
}
._mfm_jelly_ {
animation: mfm-rubberBand 1s linear infinite both;
}
._mfm_twitch_ {
animation: mfm-twitch 0.5s ease infinite;
}
._mfm_shake_ {
animation: mfm-shake 0.5s ease infinite;
}
._mfm_spin_ {
animation: mfm-spin 0.5s linear infinite;
}
._mfm_spin_[data-x] {
animation-name: mfm-spinX;
}
._mfm_spin_[data-y] {
animation-name: mfm-spinY;
}
._mfm_spin_[left] {
animation-direction: reverse;
}
._mfm_spin_[alternate] {
animation-direction: alternate;
}
._mfm_jump_ {
animation: mfm-jump 0.75s linear infinite;
}
._mfm_bounce_ {
animation: mfm-bounce 0.75s linear infinite;
transform-origin: center bottom;
}
._mfm_rainbow_ {
animation: mfm-rainbow 1s linear infinite;
}
}
}
@@ -14,7 +14,7 @@
:toggle-showing-tall="toggleShowingTall"
:toggle-expanding-subject="toggleExpandingSubject"
:toggle-showing-long-subject="toggleShowingLongSubject"
@parse-ready="$emit('parseReady', $event)"
@parseReady="$emit('parseReady', $event)"
>
<div v-if="status.poll && status.poll.options && !compact">
<Poll
@@ -64,7 +64,6 @@
</template>
<script src="./status_content.js" ></script>
<style lang="scss" src="./mfm.scss" />
<style lang="scss">
.StatusContent {
flex: 1;
@@ -76,6 +75,23 @@
height: 50px;
}
}
&.mfm-hover:not(:hover) {
.mfm {
animation: none !important;
}
}
&.mfm-disabled {
span {
font-size: 100% !important;
}
.mfm {
animation: none !important;
}
.emoji {
height: 32px !important;
}
}
}
.quote-inline,
@@ -2,7 +2,7 @@
<Modal
v-if="modalActivated"
class="status-history-modal-view"
@backdrop-clicked="closeModal"
@backdropClicked="closeModal"
>
<div class="status-history-modal-panel panel">
<div class="panel-heading">
@@ -17,7 +17,7 @@
v-for="status in history"
:key="status.id"
:statusoid="status"
:is-preview="true"
:isPreview="true"
class="conversation-status status-fadein panel-body"
/>
</div>
@@ -5,10 +5,10 @@
:bound-to="{ x: 'container' }"
@show="enter"
>
<template #trigger>
<template v-slot:trigger>
<slot />
</template>
<template #content>
<template v-slot:content>
<Status
v-if="status"
:is-preview="true"
+3 -18
View File
@@ -7,19 +7,12 @@ const StillImage = {
'imageLoadHandler',
'alt',
'height',
'width',
'noStopGifs'
'width'
],
data () {
return {
stopGifs:
!this.noStopGifs
&& (
this.$store.getters.mergedConfig.stopGifs
|| window.matchMedia('(prefers-reduced-motion: reduce)').matches
),
stopGifs: this.$store.getters.mergedConfig.stopGifs || window.matchMedia('(prefers-reduced-motion: reduce)').matches,
isAnimated: false,
isPixelArt: false,
imageTypeLabel: ''
}
},
@@ -41,19 +34,11 @@ const StillImage = {
if (!image) return
this.imageLoadHandler && this.imageLoadHandler(image)
this.detectAnimation(image)
this.detectPixelArt(image)
this.drawThumbnail()
},
onError () {
this.imageLoadError && this.imageLoadError()
},
detectPixelArt (image) {
// Safe maximum: 32x32 image, equivalent or smaller
this.isPixelArt ||= image.naturalHeight * image.naturalWidth <= 32 * 32;
// Common size for oldweb badges.
this.isPixelArt ||= image.naturalWidth == 88 && image.naturalHeight == 31;
console.log(image.src+" is "+image.naturalHeight+"x"+image.naturalWidth+" - "+(this.isPixelArt ? "pixel art" : "normal"))
},
detectAnimation (image) {
const mediaProxyAvailable = this.$store.state.instance.mediaProxyAvailable
@@ -222,7 +207,7 @@ const StillImage = {
}
context.clearRect(0, 0, canvas.width, canvas.height); // Clear the previous unscaled image
context.imageSmoothingEnabled = !this.isPixelArt;
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = 'high';
// Draw the good one for realsies
+2 -6
View File
@@ -2,13 +2,12 @@
<div
ref="still-image"
class="still-image"
:class="{ animated: animated, pixelart: isPixelArt }"
:class="{ animated: animated }"
:style="style"
>
<div
v-if="animated && imageTypeLabel"
class="image-type-label"
>
class="image-type-label">
{{ imageTypeLabel }}
</div>
<canvas
@@ -95,8 +94,5 @@
visibility: visible;
}
}
&.pixelart {
image-rendering: pixelated;
}
}
</style>
+1 -1
View File
@@ -32,7 +32,7 @@
:dive="dive ? () => dive(status.id) : undefined"
@goto="setHighlight"
@toggle-expanded="toggleExpanded"
@toggleExpanded="toggleExpanded"
/>
<div
v-if="currentReplies.length && threadShowing"
-3
View File
@@ -28,7 +28,4 @@
}
}
}
.timeline {
min-height: 1em;
}
}
@@ -4,7 +4,7 @@
class="TimelineQuickSettings"
:bound-to="{ x: 'container' }"
>
<template #content>
<template v-slot:content>
<div class="dropdown-menu">
<div v-if="loggedIn">
<button
@@ -80,7 +80,7 @@
</button>
</div>
</template>
<template #trigger>
<template v-slot:trigger>
<button class="button-unstyled">
<FAIcon icon="filter" />
</button>
@@ -9,12 +9,12 @@
@show="openMenu"
@close="() => isOpen = false"
>
<template #content>
<template v-slot:content>
<div class="timeline-menu-popover popover-default">
<TimelineMenuContent />
</div>
</template>
<template #trigger>
<template v-slot:trigger>
<button class="button-unstyled title timeline-menu-title">
<span class="timeline-title">{{ timelineName() }}</span>
<span>
@@ -62,6 +62,7 @@
:title="$t('nav.twkn_timeline_description')"
:aria-label="$t('nav.twkn_timeline_description')"
>{{ $t("nav.twkn") }}</span>
</router-link>
</li>
<li v-if="currentUser">
@@ -16,9 +16,9 @@
/>
</router-link>
<router-link
v-if="publicTimelineVisible"
:to="{ name: 'public-timeline' }"
class="nav-icon"
v-if="publicTimelineVisible"
>
<FAIcon
fixed-width
@@ -40,9 +40,9 @@
/>
</router-link>
<router-link
v-if="federatedTimelineVisible"
:to="{ name: 'public-external-timeline' }"
class="nav-icon"
v-if="federatedTimelineVisible"
>
<FAIcon
fixed-width
+1 -6
View File
@@ -66,7 +66,7 @@ export default {
return this.user.id !== this.$store.state.users.currentUser.id
},
subscribeUrl () {
// eslint-disable-next-line no-undef
const serverUrl = new URL(this.user.statusnet_profile_url)
return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus`
},
@@ -117,11 +117,6 @@ export default {
shouldConfirmMute () {
return this.mergedConfig.modalOnMute
},
compactUserInfo () {
return this.$store.getters.mergedConfig.compactUserInfo
&& (this.$store.state.interface.layoutType !== 'mobile')
&& this.switcher
},
...mapGetters(['mergedConfig'])
},
components: {

Some files were not shown because too many files have changed in this diff Show More