Compare commits
No commits in common. "shigusegubu-themes3" and "shigusegubu" have entirely different histories.
shigusegub
...
shigusegub
805 changed files with 43852 additions and 94629 deletions
5
.babelrc
Normal file
5
.babelrc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"presets": ["@babel/preset-env"],
|
||||||
|
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
|
||||||
|
"comments": true
|
||||||
|
}
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
logs/
|
|
||||||
.DS_Store
|
|
||||||
.git/
|
|
||||||
config/local.json
|
|
||||||
pleroma-backend/
|
|
||||||
test/e2e/reports/
|
|
||||||
test/e2e-playwright/test-results/
|
|
||||||
test/e2e-playwright/playwright-report/
|
|
||||||
__screenshots__/
|
|
||||||
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
name: 'Bug report'
|
|
||||||
about: 'Bug report for Pleroma FE'
|
|
||||||
labels:
|
|
||||||
- Bug
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
id: env-browser
|
|
||||||
attributes:
|
|
||||||
label: Browser and OS
|
|
||||||
description: What browser are you using, including version, and what OS are you running?
|
|
||||||
placeholder: Firefox 140, Arch Linux
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: env-instance
|
|
||||||
attributes:
|
|
||||||
label: Instance URL
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: env-backend
|
|
||||||
attributes:
|
|
||||||
label: Backend version information
|
|
||||||
description: Backend version being used. (See Settings->Show advanced->Developer)
|
|
||||||
placeholder: Pleroma BE 2.10
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: env-frontend
|
|
||||||
attributes:
|
|
||||||
label: Frontend version information
|
|
||||||
description: Frontend version being used. (See Settings->Show advanced->Developer)
|
|
||||||
placeholder: Pleroma FE 2.10
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: env-extensions
|
|
||||||
attributes:
|
|
||||||
label: Browser extensions
|
|
||||||
description: List of browser extensions you are using, like uBlock, rikaichamp etc. If none leave empty.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: env-modifications
|
|
||||||
attributes:
|
|
||||||
label: Known instance/user customizations
|
|
||||||
description: Whether you are using a Pleroma FE fork, any mods mods or instance level styles among others.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: bug-text
|
|
||||||
attributes:
|
|
||||||
label: Bug description
|
|
||||||
description: A short description of the bug. Images can be helpful.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: bug-reproducer
|
|
||||||
attributes:
|
|
||||||
label: Reproduction steps
|
|
||||||
description: Ordered list of reproduction steps needed to make the bug happen. If you don't have reproduction steps, leave this empty.
|
|
||||||
placeholder: |
|
|
||||||
1. Log in with a fresh browser session
|
|
||||||
2. Open timeline X
|
|
||||||
3. Click on button Y
|
|
||||||
4. Z broke
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
id: bug-seriousness
|
|
||||||
attributes:
|
|
||||||
label: Bug seriousness
|
|
||||||
value: |
|
|
||||||
* How annoying it is:
|
|
||||||
* How often does it happen:
|
|
||||||
* How many people does it affect:
|
|
||||||
* Is there a workaround for it:
|
|
||||||
- type: checkboxes
|
|
||||||
id: duplicate-issues
|
|
||||||
attributes:
|
|
||||||
label: Duplicate issues
|
|
||||||
hide_label: true
|
|
||||||
description: Before submitting this issue, search for same or similar issues on the [Pleroma FE bug tracker](https://git.pleroma.social/pleroma/pleroma-fe/issues).
|
|
||||||
options:
|
|
||||||
- label: I've searched for same or similar issues before submitting this issue.
|
|
||||||
required: true
|
|
||||||
visible: [form]
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
name: 'Feature request / Suggestion / Improvement'
|
|
||||||
about: 'Feature requests, suggestions and improvements for Pleroma FE'
|
|
||||||
labels:
|
|
||||||
- Feature Request / Enhancement
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: issue-text
|
|
||||||
attributes:
|
|
||||||
label: Proposal
|
|
||||||
placeholder: Make groups happen!
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
id: duplicate-issues
|
|
||||||
attributes:
|
|
||||||
label: Duplicate issues
|
|
||||||
hide_label: true
|
|
||||||
description: Before submitting this issue, search for same or similar requests on the [Pleroma FE bug tracker](https://git.pleroma.social/pleroma/pleroma-fe/issues).
|
|
||||||
options:
|
|
||||||
- label: I've searched for same or similar requests before submitting this issue.
|
|
||||||
required: true
|
|
||||||
visible: [form]
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
### Checklist
|
|
||||||
- [ ] Adding a changelog: In the `changelog.d` directory, create a file named `<code>.<type>`.
|
|
||||||
|
|
||||||
<!--
|
|
||||||
`<code>` can be anything, but we recommend using a more or less unique identifier to avoid collisions, such as the branch name.
|
|
||||||
|
|
||||||
`<type>` can be `add`, `change`, `remove`, `fix`, `security` or `skip`. `skip` is only used if there is no user-visible change in the MR (for example, only editing comments in the code). Otherwise, choose a type that corresponds to your change.
|
|
||||||
|
|
||||||
In the file, write the changelog entry. For example, if an MR adds group functionality, we can create a file named `group.add` and write `Add group functionality` in it.
|
|
||||||
|
|
||||||
If one changelog entry is not enough, you may add more. But that might mean you can split it into two MRs. Only use more than one changelog entry if you really need to (for example, when one change in the code fix two different bugs, or when refactoring).
|
|
||||||
-->
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -4,15 +4,9 @@ dist/
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
test/unit/coverage
|
test/unit/coverage
|
||||||
test/e2e/reports
|
test/e2e/reports
|
||||||
test/e2e-playwright/test-results
|
|
||||||
test/e2e-playwright/playwright-report
|
|
||||||
selenium-debug.log
|
selenium-debug.log
|
||||||
.idea/
|
.idea/
|
||||||
.gitlab-ci-local/
|
|
||||||
config/local.json
|
config/local.json
|
||||||
src/assets/emoji.json
|
src/assets/emoji.json
|
||||||
logs/
|
logs/
|
||||||
__screenshots__/
|
__screenshots__/
|
||||||
.vitest-attachments/
|
|
||||||
html/
|
|
||||||
coverage/
|
|
||||||
|
|
|
||||||
83
.gitlab-ci.yml
Normal file
83
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# This file is a template, and might need editing before it works on your project.
|
||||||
|
# Official framework image. Look for the different tagged releases at:
|
||||||
|
# https://hub.docker.com/r/library/node/tags/
|
||||||
|
image: node:18
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- check-changelog
|
||||||
|
- lint
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
# https://git.pleroma.social/help/ci/yaml/workflow.md#switch-between-branch-pipelines-and-merge-request-pipelines
|
||||||
|
workflow:
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
||||||
|
when: never
|
||||||
|
- if: $CI_COMMIT_BRANCH
|
||||||
|
|
||||||
|
check-changelog:
|
||||||
|
stage: check-changelog
|
||||||
|
image: alpine
|
||||||
|
rules:
|
||||||
|
- if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^renovate/
|
||||||
|
when: never
|
||||||
|
- if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'weblate'
|
||||||
|
when: never
|
||||||
|
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"
|
||||||
|
before_script: ''
|
||||||
|
after_script: ''
|
||||||
|
cache: {}
|
||||||
|
script:
|
||||||
|
- apk add git
|
||||||
|
- sh ./tools/check-changelog
|
||||||
|
|
||||||
|
lint:
|
||||||
|
stage: lint
|
||||||
|
script:
|
||||||
|
- yarn
|
||||||
|
- yarn lint
|
||||||
|
- yarn stylelint
|
||||||
|
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- amd64
|
||||||
|
- himem
|
||||||
|
variables:
|
||||||
|
APT_CACHE_DIR: apt-cache
|
||||||
|
script:
|
||||||
|
- mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
||||||
|
- yarn
|
||||||
|
- yarn playwright install firefox
|
||||||
|
- yarn playwright install-deps
|
||||||
|
- yarn unit-ci
|
||||||
|
artifacts:
|
||||||
|
# When the test fails, upload screenshots for better context on why it fails
|
||||||
|
paths:
|
||||||
|
- test/**/__screenshots__
|
||||||
|
when: on_failure
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
tags:
|
||||||
|
- amd64
|
||||||
|
- himem
|
||||||
|
script:
|
||||||
|
- yarn
|
||||||
|
- yarn build
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- dist/
|
||||||
|
|
||||||
|
docs-deploy:
|
||||||
|
stage: deploy
|
||||||
|
image: alpine:latest
|
||||||
|
only:
|
||||||
|
- develop@pleroma/pleroma-fe
|
||||||
|
before_script:
|
||||||
|
- apk add curl
|
||||||
|
script:
|
||||||
|
- curl -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' https://git.pleroma.social/api/v4/projects/673/trigger/pipeline
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
### Release checklist
|
|
||||||
* [ ] Bump version in `package.json`
|
|
||||||
* [ ] Compile a changelog with the `tools/collect-changelog` script
|
|
||||||
* [ ] Create an MR with an announcement to pleroma.social
|
|
||||||
#### post-merge
|
|
||||||
* [ ] Tag the release on the merge commit
|
|
||||||
* [ ] Make the tag into a Gitlab Release™
|
|
||||||
* [ ] Merge `master` into `develop` (in case the fixes are already in develop, use `git merge -s ours --no-commit` and manually merge the changelogs)
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
20.19.0
|
18.20.8
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@
|
||||||
"custom-property-pattern": null,
|
"custom-property-pattern": null,
|
||||||
"keyframes-name-pattern": null,
|
"keyframes-name-pattern": null,
|
||||||
"scss/operator-no-newline-after": null,
|
"scss/operator-no-newline-after": null,
|
||||||
"declaration-property-value-no-unknown": true,
|
|
||||||
"scss/declaration-property-value-no-unknown": true,
|
|
||||||
"declaration-block-no-redundant-longhand-properties": [
|
"declaration-block-no-redundant-longhand-properties": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
when:
|
|
||||||
- event: push
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
- event: manual
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
- test-e2e
|
|
||||||
|
|
||||||
labels:
|
|
||||||
platform: linux/amd64
|
|
||||||
memory: 'high'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
build:
|
|
||||||
image: docker.io/node:24-alpine
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache zip git
|
|
||||||
- yarn --frozen-lockfile
|
|
||||||
- yarn build
|
|
||||||
- if [ "${CI_PIPELINE_EVENT}" = "push" ] || [ "${CI_PIPELINE_EVENT}" = "manual" ]; then zip -9qr ${CI_REPO_DEFAULT_BRANCH}.zip dist/; fi
|
|
||||||
|
|
||||||
upload-artifacts:
|
|
||||||
image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
|
|
||||||
settings:
|
|
||||||
user:
|
|
||||||
from_secret: pleroma-ci-user
|
|
||||||
password:
|
|
||||||
from_secret: pleroma-ci-password
|
|
||||||
update: true
|
|
||||||
owner: 'pleroma'
|
|
||||||
package_name: 'pleroma-fe-builds'
|
|
||||||
package_version: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
file_source: ./${CI_REPO_DEFAULT_BRANCH}.zip
|
|
||||||
file_name: latest.zip
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
|
|
||||||
- event: push
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
- event: manual
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
|
|
||||||
labels:
|
|
||||||
platform: linux/amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
build:
|
|
||||||
image: docker.io/node:24-alpine
|
|
||||||
commands:
|
|
||||||
- yarn --frozen-lockfile
|
|
||||||
- yarn build
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
check-changelog:
|
|
||||||
image: docker.io/alpine:3.23
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache git
|
|
||||||
- sh ./tools/check-changelog
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
when:
|
|
||||||
- event: push
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
- event: manual
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
|
|
||||||
labels:
|
|
||||||
platform: linux/amd64
|
|
||||||
memory: 'high'
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
- test
|
|
||||||
- test-e2e
|
|
||||||
|
|
||||||
clone:
|
|
||||||
- name: git
|
|
||||||
image: woodpeckerci/plugin-git
|
|
||||||
settings:
|
|
||||||
depth: 0
|
|
||||||
partial: false
|
|
||||||
|
|
||||||
variables:
|
|
||||||
script_file_entrypoint: &script_file_entrypoint
|
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
generate-coverage:
|
|
||||||
image: mcr.microsoft.com/playwright:v1.61.0-jammy
|
|
||||||
environment:
|
|
||||||
FF_NETWORK_PER_BUILD: "true"
|
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
|
||||||
entrypoint: *script_file_entrypoint
|
|
||||||
commands:
|
|
||||||
- yarn --frozen-lockfile
|
|
||||||
- yarn unit-ci-coverage
|
|
||||||
|
|
||||||
code-analysis:
|
|
||||||
image: sonarsource/sonar-scanner-cli:11
|
|
||||||
environment:
|
|
||||||
SONAR_TOKEN:
|
|
||||||
from_secret: sonarqube-token
|
|
||||||
entrypoint: sonar-scanner
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
|
|
||||||
- event: push
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
- event: manual
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
install-depends:
|
|
||||||
image: &node-image
|
|
||||||
docker.io/node:24-alpine
|
|
||||||
commands:
|
|
||||||
- yarn --frozen-lockfile
|
|
||||||
|
|
||||||
biome:
|
|
||||||
image: *node-image
|
|
||||||
depends_on: install-depends
|
|
||||||
commands:
|
|
||||||
- yarn ci-biome
|
|
||||||
|
|
||||||
stylelint:
|
|
||||||
image: *node-image
|
|
||||||
depends_on: install-depends
|
|
||||||
commands:
|
|
||||||
- yarn ci-stylelint
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
|
|
||||||
- event: push
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
- event: manual
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
|
|
||||||
labels:
|
|
||||||
platform: linux/amd64
|
|
||||||
memory: 'high'
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
- test
|
|
||||||
|
|
||||||
variables:
|
|
||||||
artifacts_uploader_settings: &artifacts_uploader_settings
|
|
||||||
user:
|
|
||||||
from_secret: pleroma-ci-user
|
|
||||||
password:
|
|
||||||
from_secret: pleroma-ci-password
|
|
||||||
owner: 'pleroma'
|
|
||||||
package_name: 'pleroma-fe-test-artifacts'
|
|
||||||
script_file_entrypoint: &script_file_entrypoint
|
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
test:
|
|
||||||
image: mcr.microsoft.com/playwright:v1.61.0-jammy
|
|
||||||
entrypoint: *script_file_entrypoint
|
|
||||||
environment:
|
|
||||||
APT_CACHE_DIR: apt-cache
|
|
||||||
DEBIAN_FRONTEND: noninteractive
|
|
||||||
E2E_BASE_URL: http://localhost:8099
|
|
||||||
FF_NETWORK_PER_BUILD: "true"
|
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
|
||||||
VITE_PROXY_ORIGIN: "http://pleroma:4000"
|
|
||||||
VITE_PROXY_TARGET: "http://pleroma:4000"
|
|
||||||
commands:
|
|
||||||
- |
|
|
||||||
if [ "${CI_PIPELINE_EVENT}" != "pull_request" ]; then
|
|
||||||
mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
|
||||||
apt-get install -y zip
|
|
||||||
fi
|
|
||||||
- npm install -g yarn@1.22.22
|
|
||||||
- yarn --frozen-lockfile
|
|
||||||
- |
|
|
||||||
echo "-- Waiting for Pleroma API..."
|
|
||||||
api_ok="false"
|
|
||||||
for _i in $(seq 1 120); do
|
|
||||||
if wget -qO- http://pleroma:4000/api/v1/instance >/dev/null 2>&1; then
|
|
||||||
api_ok="true"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1s
|
|
||||||
done
|
|
||||||
if [ "$api_ok" != "true" ]; then
|
|
||||||
echo "Timed out waiting for Pleroma API to become available"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- |
|
|
||||||
if ! yarn e2e:pw; then
|
|
||||||
[ "${CI_PIPELINE_EVENT}" = "pull_request" ] || zip -9qr ${CI_COMMIT_SHA:0:8}-e2e.zip ./test/e2e-playwright/test-results ./test/e2e-playwright/playwright-report
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
upload-artifacts:
|
|
||||||
image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
|
|
||||||
when:
|
|
||||||
- event: push
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
status: [failure]
|
|
||||||
- event: manual
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
status: [failure]
|
|
||||||
settings:
|
|
||||||
<<: *artifacts_uploader_settings
|
|
||||||
package_version: ${CI_REPO_DEFAULT_BRANCH}-${CI_COMMIT_SHA:0:8}
|
|
||||||
file_source: ./${CI_COMMIT_SHA:0:8}-e2e.zip
|
|
||||||
file_name: ${CI_COMMIT_SHA:0:8}-e2e.zip
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: docker.io/postgres:13-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: pleroma_test
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
|
|
||||||
pleroma:
|
|
||||||
image: git.pleroma.social/pleroma/pleroma:stable-e2e
|
|
||||||
environment:
|
|
||||||
ADMIN_EMAIL: "admin@example.com"
|
|
||||||
NOTIFY_EMAIL: "admin@example.com"
|
|
||||||
DB_USER: postgres
|
|
||||||
DB_PASS: postgres
|
|
||||||
DB_NAME: pleroma_test
|
|
||||||
DB_HOST: postgres
|
|
||||||
INSTANCE_NAME: Pleroma E2E
|
|
||||||
E2E_ADMIN_USERNAME: admin
|
|
||||||
E2E_ADMIN_PASSWORD: adminadmin
|
|
||||||
E2E_ADMIN_EMAIL: "admin@example.com"
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate" && not(CI_COMMIT_SOURCE_BRANCH startsWith "renovate/")'
|
|
||||||
- event: push
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
- event: manual
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
|
|
||||||
labels:
|
|
||||||
platform: linux/amd64
|
|
||||||
memory: 'high'
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
|
|
||||||
variables:
|
|
||||||
artifacts_uploader_settings: &artifacts_uploader_settings
|
|
||||||
user:
|
|
||||||
from_secret: pleroma-ci-user
|
|
||||||
password:
|
|
||||||
from_secret: pleroma-ci-password
|
|
||||||
owner: 'pleroma'
|
|
||||||
package_name: 'pleroma-fe-test-artifacts'
|
|
||||||
script_file_entrypoint: &script_file_entrypoint
|
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
test:
|
|
||||||
image: mcr.microsoft.com/playwright:v1.61.0-jammy
|
|
||||||
environment:
|
|
||||||
APT_CACHE_DIR: apt-cache
|
|
||||||
DEBIAN_FRONTEND: noninteractive
|
|
||||||
FF_NETWORK_PER_BUILD: "true"
|
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
|
||||||
entrypoint: *script_file_entrypoint
|
|
||||||
commands:
|
|
||||||
- npm install --global yarn
|
|
||||||
- |
|
|
||||||
if [ "${CI_PIPELINE_EVENT}" != "pull_request" ]; then
|
|
||||||
mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
|
||||||
apt-get -y install zip
|
|
||||||
fi
|
|
||||||
- yarn --frozen-lockfile
|
|
||||||
- |
|
|
||||||
if ! yarn unit-ci; then
|
|
||||||
[ "${CI_PIPELINE_EVENT}" = "pull_request" ] || zip -9qr ${CI_COMMIT_SHA:0:8}-screenshots.zip $(find . -type d -name __screenshots__)
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
upload-artifacts:
|
|
||||||
image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
|
|
||||||
when:
|
|
||||||
- event: push
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
status: [failure]
|
|
||||||
- event: manual
|
|
||||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
|
||||||
status: [failure]
|
|
||||||
settings:
|
|
||||||
<<: *artifacts_uploader_settings
|
|
||||||
package_version: ${CI_REPO_DEFAULT_BRANCH}-${CI_COMMIT_SHA:0:8}
|
|
||||||
file_source: ./${CI_COMMIT_SHA:0:8}-screenshots.zip
|
|
||||||
file_name: ${CI_COMMIT_SHA:0:8}-screenshots.zip
|
|
||||||
122
CHANGELOG.md
122
CHANGELOG.md
|
|
@ -2,124 +2,6 @@
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## 2.11.1
|
|
||||||
### Fixed
|
|
||||||
- User note not working
|
|
||||||
|
|
||||||
## 2.11.0
|
|
||||||
### Added
|
|
||||||
- Initial MFM rendering support
|
|
||||||
- Button to remove all drafts
|
|
||||||
- Option to remove forced aspect ratio for user profiles (requested)
|
|
||||||
- Showing user tags (MRF policies for user + custom if present)
|
|
||||||
- Version information now is also in about page
|
|
||||||
- Mention autosuggest now sorts by recent activity
|
|
||||||
- Non-square emoji support (toggleable by user)
|
|
||||||
- Displaying other user's backgrounds (if supported by BE)
|
|
||||||
- Add quoting by URL and in replies
|
|
||||||
- Settings synchronization
|
|
||||||
- User highlight synchronization
|
|
||||||
- User administration + post scope/sensitivity admin change support
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Migrated to Vite 8 and optimized our imports, more stuff is loaded on-demand, reducing the initial load time and transfer size
|
|
||||||
- Overall improved spacing in status action buttons and post form
|
|
||||||
- Logout confirm button is now dangerous
|
|
||||||
- Reply/quote now is a radio group and wraps, fixes overflow on languages where labels are too wide
|
|
||||||
- Personal note input is now bigger
|
|
||||||
- Moved "edit pinned" to the bottom for status action buttons.
|
|
||||||
- Dots status action button drops down instead of up to avoid hiding the action buttons
|
|
||||||
- Improved attachment description (alt text) input and display
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fix HTML attribute parsing for escaped quotes
|
|
||||||
- Fix emojis breaking user bio/description editing
|
|
||||||
- Navbar wide logo cropping search input
|
|
||||||
- Danger buttons being too bright
|
|
||||||
- User background upload failure no longer breaks new uploads + displays an error
|
|
||||||
- Importing theme from old theme editor
|
|
||||||
- Removed duplicate federationpolicy entry in admin tab
|
|
||||||
- Repeater name overflowing content
|
|
||||||
- Reply popover is now shown if replied-to status is muted
|
|
||||||
- Second language input not having header
|
|
||||||
- Post form's bottom left buttons not showing their toggled state
|
|
||||||
- Some font overrides not working
|
|
||||||
- Popovers opening outside of window's boundaries
|
|
||||||
- Occasional blank page when showing new posts
|
|
||||||
- Fixed status action mute hiding itself on click
|
|
||||||
- Fix reply form crash when quote-reply settings are unavailable
|
|
||||||
|
|
||||||
|
|
||||||
## 2.10.1
|
|
||||||
### Fixed
|
|
||||||
- fixed being unable to set actor type from profile page
|
|
||||||
- fixed error when clicking mute menu itself (instead of submenu items)
|
|
||||||
- fixed mute -> domain status submenu not working
|
|
||||||
|
|
||||||
### Internal
|
|
||||||
- Add playwright E2E-tests with an optional docker-based backend
|
|
||||||
|
|
||||||
## 2.10.0
|
|
||||||
### Changed
|
|
||||||
- Temporary changes modal now shows actual countdown instead of fixed timeout
|
|
||||||
- Disabled elements are more disabled now
|
|
||||||
- Rearranged and split settings to make more sense and be less of a wall of text
|
|
||||||
- On mobile settings now take up full width and presented in navigation style
|
|
||||||
improved styles for settings
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Most of the remaining AdminFE tabs were added into Admin Dashboard
|
|
||||||
- It's now possible to customize PWA Manfiest from PleromaFE
|
|
||||||
- Make every configuration option default-overridable by instance admins
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Fixed settings not appearing if user never touched "show advanced" toggle
|
|
||||||
- Fix display of the broken/deleted/banned users
|
|
||||||
- Fixed incorrect emoji display in post interaction lists
|
|
||||||
- Fixed list title not being saved when editing
|
|
||||||
- Fixed poll notifications not being expandable
|
|
||||||
|
|
||||||
|
|
||||||
## 2.9.3
|
|
||||||
### Fixed
|
|
||||||
- Being unable to update profile
|
|
||||||
|
|
||||||
## 2.9.2
|
|
||||||
### Changed
|
|
||||||
- BREAKING: due to some internal technical changes logging into AdminFE through PleromaFE is no longer possible
|
|
||||||
- User card/profile got an overhaul
|
|
||||||
- Profile editing overhaul
|
|
||||||
- Visually combined subject and content fields in post form
|
|
||||||
- Moved post form's emoji button into input field
|
|
||||||
- Minor visual changes and fixes
|
|
||||||
- Clicking on fav/rt/emoji notifications' contents expands/collapses it
|
|
||||||
- Reduced time taken processing theme by half
|
|
||||||
- Splash screen only appears if loading takes more than 2 seconds
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Mutes received an update, adding support for regex, muting based on username and expiration time.
|
|
||||||
- Mutes are now synchronized across sessions
|
|
||||||
- Support for expiring mutes and blocks (if available)
|
|
||||||
- Clicking on emoji shows bigger version of it alongside with its shortcode
|
|
||||||
- Admins also are able to copy it into a local pack
|
|
||||||
- Added support for Akkoma and IceShrimp.NET backends
|
|
||||||
- Compatibility with stricter CSP (Akkoma backend)
|
|
||||||
- Added a way to upload new packs from a URL or ZIP file via the Admin Dashboard
|
|
||||||
- Unify show/hide content buttons
|
|
||||||
- Add support for detachable scrollTop button
|
|
||||||
- Option to left-align user bio
|
|
||||||
- Cache assets and emojis with service worker
|
|
||||||
- Indicate currently active V3 theme as a body element class
|
|
||||||
- Add arithmetic blend ISS function
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Display counter for status action buttons when they are in the menu
|
|
||||||
- Fix bookmark button alignment in the extra actions menu
|
|
||||||
- Instance favicons are no longer stretched
|
|
||||||
- A lot more scalable UI fixes
|
|
||||||
- Emoji picker now should work fine when emoji size is increased
|
|
||||||
|
|
||||||
## 2.8.0
|
## 2.8.0
|
||||||
### Changed
|
### Changed
|
||||||
- BREAKING: static/img/nsfw.2958239.png is now static/img/nsfw.DepQPhG0.png, which may affect people who specify exactly this path as the cover image
|
- BREAKING: static/img/nsfw.2958239.png is now static/img/nsfw.DepQPhG0.png, which may affect people who specify exactly this path as the cover image
|
||||||
|
|
@ -152,8 +34,8 @@ This does not guarantee that browsers will or will not work.
|
||||||
- Support displaying time in absolute format
|
- Support displaying time in absolute format
|
||||||
- Add draft management system
|
- Add draft management system
|
||||||
- Compress most kinds of images on upload.
|
- Compress most kinds of images on upload.
|
||||||
- Added option to always convert images to JPEG format instead of using WebP when compressing images.
|
- Added option to always convert images to JPEG format instead of using WebP when compressing images.
|
||||||
- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload.
|
- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload.
|
||||||
- Inform users that Smithereen public polls are public
|
- Inform users that Smithereen public polls are public
|
||||||
- Splash screen + loading indicator to make process of identifying initialization issues and load performance
|
- Splash screen + loading indicator to make process of identifying initialization issues and load performance
|
||||||
- UI for making v3 themes and palettes, support for bundling v3 themes
|
- UI for making v3 themes and palettes, support for bundling v3 themes
|
||||||
|
|
|
||||||
17
README.md
17
README.md
|
|
@ -1,14 +1,12 @@
|
||||||
# Pleroma-FE
|
# Pleroma-FE
|
||||||
|
|
||||||
> Highly-customizable frontend designed for Pleroma.
|
> Highly-customizable frontend designed for Pleroma.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[](https://ci.pleroma.com/repos/2) [](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) <a href="https://translate.pleroma.social/engage/pleroma/"><img src="https://translate.pleroma.social/widget/pleroma/pleroma-fe/svg-badge.svg" alt="Translation status"></a>
|
|
||||||
|
|
||||||
# For Translators
|
# For Translators
|
||||||
|
|
||||||
To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/languages.js).
|
To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/languages.js).
|
||||||
|
|
||||||
Pleroma-FE will set your language by your browser locale, but you can change language in settings.
|
Pleroma-FE will set your language by your browser locale, but you can change language in settings.
|
||||||
|
|
||||||
|
|
@ -34,15 +32,10 @@ yarn unit
|
||||||
|
|
||||||
# For Contributors:
|
# For Contributors:
|
||||||
|
|
||||||
You can make local build proxy requests to specific instance with an environment variable `VITE_PROXY_TARGET`, generally it is a good idea to also set port for that specific proxy, if you intend on testing multiple instances, i.e.:
|
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:
|
||||||
|
|
||||||
```bash
|
* `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.
|
||||||
yarn && PORT=8080 VITE_PROXY_TARGET=https://coolinstance.tld yarn dev
|
* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode.
|
||||||
```
|
|
||||||
|
|
||||||
You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/src/config/local.example.json)) to enable some convenience dev options:
|
|
||||||
|
|
||||||
* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/pleroma/frontend_configurations`. 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.
|
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.
|
||||||
|
|
||||||
|
|
|
||||||
161
biome.json
161
biome.json
|
|
@ -1,161 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://biomejs.dev/schemas/2.5.11/schema.json",
|
|
||||||
"vcs": {
|
|
||||||
"enabled": true,
|
|
||||||
"clientKind": "git",
|
|
||||||
"useIgnoreFile": true
|
|
||||||
},
|
|
||||||
"files": {
|
|
||||||
"includes": ["**", "!!**/dist", "!!tools/emojis.json"]
|
|
||||||
},
|
|
||||||
"formatter": {
|
|
||||||
"enabled": true,
|
|
||||||
"indentStyle": "space"
|
|
||||||
},
|
|
||||||
"linter": {
|
|
||||||
"enabled": true,
|
|
||||||
"domains": {
|
|
||||||
"vue": "recommended"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"preset": "none",
|
|
||||||
"complexity": {
|
|
||||||
"noAdjacentSpacesInRegex": "error",
|
|
||||||
"noExtraBooleanCast": "error",
|
|
||||||
"noUselessCatch": "error",
|
|
||||||
"noUselessEscapeInRegex": "error"
|
|
||||||
},
|
|
||||||
"correctness": {
|
|
||||||
"noConstAssign": "error",
|
|
||||||
"noConstantCondition": "error",
|
|
||||||
"noEmptyCharacterClassInRegex": "error",
|
|
||||||
"noEmptyPattern": "error",
|
|
||||||
"noGlobalObjectCalls": "error",
|
|
||||||
"noInvalidBuiltinInstantiation": "error",
|
|
||||||
"noInvalidConstructorSuper": "error",
|
|
||||||
"noNonoctalDecimalEscape": "error",
|
|
||||||
"noPrecisionLoss": "error",
|
|
||||||
"noSelfAssign": "error",
|
|
||||||
"noSetterReturn": "error",
|
|
||||||
"noSwitchDeclarations": "error",
|
|
||||||
"noUndeclaredVariables": "error",
|
|
||||||
"noUnreachable": "error",
|
|
||||||
"noUnreachableSuper": "error",
|
|
||||||
"noUnsafeFinally": "error",
|
|
||||||
"noUnsafeOptionalChaining": "error",
|
|
||||||
"noUnusedLabels": "error",
|
|
||||||
"noUnusedPrivateClassMembers": "error",
|
|
||||||
"noUnusedVariables": "error",
|
|
||||||
"noUnusedImports": "error",
|
|
||||||
"useIsNan": "error",
|
|
||||||
"useValidForDirection": "error",
|
|
||||||
"useValidTypeof": "error",
|
|
||||||
"useYield": "error"
|
|
||||||
},
|
|
||||||
"suspicious": {
|
|
||||||
"noAsyncPromiseExecutor": "error",
|
|
||||||
"noCatchAssign": "error",
|
|
||||||
"noClassAssign": "error",
|
|
||||||
"noCompareNegZero": "error",
|
|
||||||
"noConstantBinaryExpressions": "error",
|
|
||||||
"noControlCharactersInRegex": "error",
|
|
||||||
"noDebugger": "error",
|
|
||||||
"noDuplicateCase": "error",
|
|
||||||
"noDuplicateClassMembers": "error",
|
|
||||||
"noDuplicateElseIf": "error",
|
|
||||||
"noDuplicateObjectKeys": "error",
|
|
||||||
"noDuplicateParameters": "error",
|
|
||||||
"noEmptyBlockStatements": "error",
|
|
||||||
"noFallthroughSwitchClause": "error",
|
|
||||||
"noFunctionAssign": "error",
|
|
||||||
"noGlobalAssign": "error",
|
|
||||||
"noImportAssign": "error",
|
|
||||||
"noIrregularWhitespace": "error",
|
|
||||||
"noMisleadingCharacterClass": "error",
|
|
||||||
"noPrototypeBuiltins": "error",
|
|
||||||
"noRedeclare": "error",
|
|
||||||
"noShadowRestrictedNames": "error",
|
|
||||||
"noSparseArray": "error",
|
|
||||||
"noUnsafeNegation": "error",
|
|
||||||
"noUselessRegexBackrefs": "error",
|
|
||||||
"noWith": "error",
|
|
||||||
"useGetterReturn": "error"
|
|
||||||
},
|
|
||||||
"style": {
|
|
||||||
"useVueMultiWordComponentNames": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"javascript": {
|
|
||||||
"formatter": {
|
|
||||||
"quoteStyle": "single",
|
|
||||||
"semicolons": "asNeeded"
|
|
||||||
},
|
|
||||||
"globals": []
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"includes": ["**/*.spec.js", "test/fixtures/*.js"],
|
|
||||||
"javascript": {
|
|
||||||
"globals": [
|
|
||||||
"vi",
|
|
||||||
"describe",
|
|
||||||
"it",
|
|
||||||
"test",
|
|
||||||
"expect",
|
|
||||||
"before",
|
|
||||||
"beforeEach",
|
|
||||||
"after",
|
|
||||||
"afterEach"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"includes": ["**/*.vue"],
|
|
||||||
"linter": {
|
|
||||||
"rules": {
|
|
||||||
"style": {
|
|
||||||
"useConst": "off",
|
|
||||||
"useImportType": "off"
|
|
||||||
},
|
|
||||||
"correctness": {
|
|
||||||
"noUnusedVariables": "off",
|
|
||||||
"noUnusedImports": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"assist": {
|
|
||||||
"enabled": true,
|
|
||||||
"actions": {
|
|
||||||
"source": {
|
|
||||||
"organizeImports": {
|
|
||||||
"level": "on",
|
|
||||||
"options": {
|
|
||||||
"groups": [
|
|
||||||
[":NODE:", ":PACKAGE:", "!src/**", "!@fortawesome/**"],
|
|
||||||
":BLANK_LINE:",
|
|
||||||
[":PATH:", "src/components/**"],
|
|
||||||
":BLANK_LINE:",
|
|
||||||
[":PATH:", "src/stores/**"],
|
|
||||||
":BLANK_LINE:",
|
|
||||||
[":PATH:", "src/composables/**"],
|
|
||||||
":BLANK_LINE:",
|
|
||||||
[
|
|
||||||
":PATH:",
|
|
||||||
"src/**",
|
|
||||||
"src/stores/**",
|
|
||||||
"src/components/**",
|
|
||||||
"src/composables/**"
|
|
||||||
],
|
|
||||||
":BLANK_LINE:",
|
|
||||||
"@fortawesome/fontawesome-svg-core",
|
|
||||||
"@fortawesome/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
35
build/check-versions.mjs
Normal file
35
build/check-versions.mjs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import semver from 'semver'
|
||||||
|
import chalk from 'chalk'
|
||||||
|
|
||||||
|
import packageConfig from '../package.json' with { type: 'json' }
|
||||||
|
|
||||||
|
var versionRequirements = [
|
||||||
|
{
|
||||||
|
name: 'node',
|
||||||
|
currentVersion: semver.clean(process.version),
|
||||||
|
versionRequirement: packageConfig.engines.node
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const warnings = []
|
||||||
|
for (let i = 0; i < versionRequirements.length; i++) {
|
||||||
|
const mod = versionRequirements[i]
|
||||||
|
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
||||||
|
warnings.push(mod.name + ': ' +
|
||||||
|
chalk.red(mod.currentVersion) + ' should be ' +
|
||||||
|
chalk.green(mod.versionRequirement)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warnings.length) {
|
||||||
|
console.warn(chalk.yellow('\nTo use this template, you must update following to modules:\n'))
|
||||||
|
for (let i = 0; i < warnings.length; i++) {
|
||||||
|
const warning = warnings[i]
|
||||||
|
console.warn(' ' + warning)
|
||||||
|
}
|
||||||
|
console.warn()
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
import childProcess from 'node:child_process'
|
import childProcess from 'child_process'
|
||||||
|
|
||||||
export const getCommitHash = () => {
|
export const getCommitHash = (() => {
|
||||||
const subst = '$Format:%h$'
|
const subst = "$Format:%h$"
|
||||||
if (!/Format:/.exec(subst)) {
|
if(!subst.match(/Format:/)) {
|
||||||
return subst
|
return subst
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
return childProcess
|
return childProcess
|
||||||
.execSync(
|
.execSync('git rev-parse --short HEAD')
|
||||||
'PATH=/usr/bin:/bin:/usr/local/bin:/sbin:/usr/sbin git rev-parse --short HEAD',
|
|
||||||
)
|
|
||||||
.toString()
|
.toString()
|
||||||
.trim()
|
.trim()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -17,4 +15,4 @@ export const getCommitHash = () => {
|
||||||
return 'UNKNOWN'
|
return 'UNKNOWN'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { cp } from 'node:fs/promises'
|
|
||||||
import { resolve } from 'node:path'
|
|
||||||
import serveStatic from 'serve-static'
|
import serveStatic from 'serve-static'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
import { cp } from 'node:fs/promises'
|
||||||
|
|
||||||
const getPrefix = (s) => {
|
const getPrefix = s => {
|
||||||
const padEnd = s.endsWith('/') ? s : s + '/'
|
const padEnd = s.endsWith('/') ? s : s + '/'
|
||||||
return padEnd.startsWith('/') ? padEnd : '/' + padEnd
|
return padEnd.startsWith('/') ? padEnd : '/' + padEnd
|
||||||
}
|
}
|
||||||
|
|
@ -13,31 +13,28 @@ const copyPlugin = ({ inUrl, inFs }) => {
|
||||||
let copyTarget
|
let copyTarget
|
||||||
const handler = serveStatic(inFs)
|
const handler = serveStatic(inFs)
|
||||||
|
|
||||||
return [
|
return [{
|
||||||
{
|
name: 'copy-plugin-serve',
|
||||||
name: 'copy-plugin-serve',
|
apply: 'serve',
|
||||||
apply: 'serve',
|
configureServer (server) {
|
||||||
configureServer(server) {
|
server.middlewares.use(prefix, handler)
|
||||||
server.middlewares.use(prefix, handler)
|
}
|
||||||
},
|
}, {
|
||||||
|
name: 'copy-plugin-build',
|
||||||
|
apply: 'build',
|
||||||
|
configResolved (config) {
|
||||||
|
copyTarget = resolve(config.root, config.build.outDir, subdir)
|
||||||
},
|
},
|
||||||
{
|
closeBundle: {
|
||||||
name: 'copy-plugin-build',
|
order: 'post',
|
||||||
apply: 'build',
|
sequential: true,
|
||||||
configResolved(config) {
|
async handler () {
|
||||||
copyTarget = resolve(config.root, config.build.outDir, subdir)
|
console.log(`Copying '${inFs}' to ${copyTarget}...`)
|
||||||
},
|
await cp(inFs, copyTarget, { recursive: true })
|
||||||
closeBundle: {
|
console.log('Done.')
|
||||||
order: 'post',
|
}
|
||||||
sequential: true,
|
}
|
||||||
async handler() {
|
}]
|
||||||
console.info(`Copying '${inFs}' to ${copyTarget}...`)
|
|
||||||
await cp(inFs, copyTarget, { recursive: true })
|
|
||||||
console.info('Done.')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default copyPlugin
|
export default copyPlugin
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,21 @@
|
||||||
import { access } from 'node:fs/promises'
|
|
||||||
import { resolve } from 'node:path'
|
import { resolve } from 'node:path'
|
||||||
|
import { access } from 'node:fs/promises'
|
||||||
import { languages } from '../src/i18n/languages.js'
|
import { languages, langCodeToCldrName } from '../src/i18n/languages.js'
|
||||||
|
|
||||||
const annotationsImportPrefix = '@kazvmoe-infra/unicode-emoji-json/annotations/'
|
const annotationsImportPrefix = '@kazvmoe-infra/unicode-emoji-json/annotations/'
|
||||||
const specialAnnotationsLocale = {
|
const specialAnnotationsLocale = {
|
||||||
ja_easy: 'ja',
|
ja_easy: 'ja'
|
||||||
}
|
}
|
||||||
|
|
||||||
const internalToAnnotationsLocale = (internal) =>
|
const internalToAnnotationsLocale = (internal) => specialAnnotationsLocale[internal] || internal
|
||||||
specialAnnotationsLocale[internal] || internal
|
|
||||||
|
|
||||||
// This gets all the annotations that are accessible (whose language
|
// This gets all the annotations that are accessible (whose language
|
||||||
// can be chosen in the settings). Data for other languages are
|
// can be chosen in the settings). Data for other languages are
|
||||||
// discarded because there is no way for it to be fetched.
|
// discarded because there is no way for it to be fetched.
|
||||||
const getAllAccessibleAnnotations = async (projectRoot) => {
|
const getAllAccessibleAnnotations = async (projectRoot) => {
|
||||||
const imports = (
|
const imports = (await Promise.all(
|
||||||
await Promise.all(
|
languages
|
||||||
languages.map(async (lang) => {
|
.map(async lang => {
|
||||||
const destLang = internalToAnnotationsLocale(lang)
|
const destLang = internalToAnnotationsLocale(lang)
|
||||||
const importModule = `${annotationsImportPrefix}${destLang}.json`
|
const importModule = `${annotationsImportPrefix}${destLang}.json`
|
||||||
const importFile = resolve(projectRoot, 'node_modules', importModule)
|
const importFile = resolve(projectRoot, 'node_modules', importModule)
|
||||||
|
|
@ -25,18 +23,11 @@ const getAllAccessibleAnnotations = async (projectRoot) => {
|
||||||
await access(importFile)
|
await access(importFile)
|
||||||
return `'${lang}': () => import('${importModule}')`
|
return `'${lang}': () => import('${importModule}')`
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.match(/ENOENT/)) {
|
|
||||||
console.warn(`Missing emoji annotations locale: ${destLang}`)
|
|
||||||
} else {
|
|
||||||
console.error('test', e.message)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}),
|
})))
|
||||||
)
|
.filter(k => k)
|
||||||
)
|
.join(',\n')
|
||||||
.filter(Boolean)
|
|
||||||
.join(',\n')
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
export const annotationsLoader = {
|
export const annotationsLoader = {
|
||||||
|
|
@ -52,21 +43,21 @@ const emojisPlugin = () => {
|
||||||
let projectRoot
|
let projectRoot
|
||||||
return {
|
return {
|
||||||
name: 'emojis-plugin',
|
name: 'emojis-plugin',
|
||||||
configResolved(conf) {
|
configResolved (conf) {
|
||||||
projectRoot = conf.root
|
projectRoot = conf.root
|
||||||
},
|
},
|
||||||
resolveId(id) {
|
resolveId (id) {
|
||||||
if (id === emojiAnnotationsId) {
|
if (id === emojiAnnotationsId) {
|
||||||
return emojiAnnotationsIdResolved
|
return emojiAnnotationsIdResolved
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
async load(id) {
|
async load (id) {
|
||||||
if (id === emojiAnnotationsIdResolved) {
|
if (id === emojiAnnotationsIdResolved) {
|
||||||
return await getAllAccessibleAnnotations(projectRoot)
|
return await getAllAccessibleAnnotations(projectRoot)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
28
build/msw_plugin.js
Normal file
28
build/msw_plugin.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
|
||||||
|
const target = 'node_modules/msw/lib/mockServiceWorker.js'
|
||||||
|
|
||||||
|
const mswPlugin = () => {
|
||||||
|
let projectRoot
|
||||||
|
return {
|
||||||
|
name: 'msw-plugin',
|
||||||
|
apply: 'serve',
|
||||||
|
configResolved (conf) {
|
||||||
|
projectRoot = conf.root
|
||||||
|
},
|
||||||
|
configureServer (server) {
|
||||||
|
server.middlewares.use(async (req, res, next) => {
|
||||||
|
if (req.path === '/mockServiceWorker.js') {
|
||||||
|
const file = await readFile(resolve(projectRoot, target))
|
||||||
|
res.set('Content-Type', 'text/javascript')
|
||||||
|
res.send(file)
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default mswPlugin
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
|
import { languages, langCodeToJsonName } from '../src/i18n/languages.js'
|
||||||
import { readFile } from 'node:fs/promises'
|
import { readFile } from 'node:fs/promises'
|
||||||
import { dirname, resolve } from 'node:path'
|
import { dirname, resolve } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
import { langCodeToJsonName, languages } from '../src/i18n/languages.js'
|
|
||||||
|
|
||||||
const i18nDir = resolve(
|
const i18nDir = resolve(
|
||||||
dirname(dirname(fileURLToPath(import.meta.url))),
|
dirname(dirname(fileURLToPath(import.meta.url))),
|
||||||
'src/i18n',
|
'src/i18n'
|
||||||
)
|
)
|
||||||
|
|
||||||
export const i18nFiles = languages.reduce((acc, lang) => {
|
export const i18nFiles = languages.reduce((acc, lang) => {
|
||||||
|
|
@ -17,15 +16,13 @@ export const i18nFiles = languages.reduce((acc, lang) => {
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
export const generateServiceWorkerMessages = async () => {
|
export const generateServiceWorkerMessages = async () => {
|
||||||
const msgArray = await Promise.all(
|
const msgArray = await Promise.all(Object.entries(i18nFiles).map(async ([lang, file]) => {
|
||||||
Object.entries(i18nFiles).map(async ([lang, file]) => {
|
const fileContent = await readFile(file, 'utf-8')
|
||||||
const fileContent = await readFile(file, 'utf-8')
|
const msg = {
|
||||||
const msg = {
|
notifications: JSON.parse(fileContent).notifications || {}
|
||||||
notifications: JSON.parse(fileContent).notifications || {},
|
}
|
||||||
}
|
return [lang, msg]
|
||||||
return [lang, msg]
|
}))
|
||||||
}),
|
|
||||||
)
|
|
||||||
return msgArray.reduce((acc, [lang, msg]) => {
|
return msgArray.reduce((acc, [lang, msg]) => {
|
||||||
acc[lang] = msg
|
acc[lang] = msg
|
||||||
return acc
|
return acc
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { dirname, resolve } from 'node:path'
|
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import { exactRegex } from '@rolldown/pluginutils'
|
import { dirname, resolve } from 'node:path'
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
import { build } from 'vite'
|
import { build } from 'vite'
|
||||||
|
import * as esbuild from 'esbuild'
|
||||||
import {
|
import { generateServiceWorkerMessages, i18nFiles } from './service_worker_messages.js'
|
||||||
generateServiceWorkerMessages,
|
|
||||||
i18nFiles,
|
|
||||||
} from './service_worker_messages.js'
|
|
||||||
|
|
||||||
const getSWMessagesAsText = async () => {
|
const getSWMessagesAsText = async () => {
|
||||||
const messages = await generateServiceWorkerMessages()
|
const messages = await generateServiceWorkerMessages()
|
||||||
|
|
@ -14,23 +11,123 @@ const getSWMessagesAsText = async () => {
|
||||||
}
|
}
|
||||||
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
|
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
|
||||||
|
|
||||||
|
const swEnvName = 'virtual:pleroma-fe/service_worker_env'
|
||||||
|
const swEnvNameResolved = '\0' + swEnvName
|
||||||
|
const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };`
|
||||||
|
const getProdSwEnv = ({ assets }) => `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
|
||||||
|
|
||||||
|
export const devSwPlugin = ({
|
||||||
|
swSrc,
|
||||||
|
swDest,
|
||||||
|
transformSW,
|
||||||
|
alias
|
||||||
|
}) => {
|
||||||
|
const swFullSrc = resolve(projectRoot, swSrc)
|
||||||
|
const esbuildAlias = {}
|
||||||
|
Object.entries(alias).forEach(([source, dest]) => {
|
||||||
|
esbuildAlias[source] = dest.startsWith('/') ? projectRoot + dest : dest
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'dev-sw-plugin',
|
||||||
|
apply: 'serve',
|
||||||
|
configResolved (conf) {
|
||||||
|
},
|
||||||
|
resolveId (id) {
|
||||||
|
const name = id.startsWith('/') ? id.slice(1) : id
|
||||||
|
if (name === swDest) {
|
||||||
|
return swFullSrc
|
||||||
|
} else if (name === swEnvName) {
|
||||||
|
return swEnvNameResolved
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
async load (id) {
|
||||||
|
if (id === swFullSrc) {
|
||||||
|
return readFile(swFullSrc, 'utf-8')
|
||||||
|
} else if (id === swEnvNameResolved) {
|
||||||
|
return getDevSwEnv()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* vite does not bundle the service worker
|
||||||
|
* during dev, and firefox does not support ESM as service worker
|
||||||
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=1360870
|
||||||
|
*/
|
||||||
|
async transform (code, id) {
|
||||||
|
if (id === swFullSrc && transformSW) {
|
||||||
|
const res = await esbuild.build({
|
||||||
|
entryPoints: [swSrc],
|
||||||
|
bundle: true,
|
||||||
|
write: false,
|
||||||
|
outfile: 'sw-pleroma.js',
|
||||||
|
alias: esbuildAlias,
|
||||||
|
plugins: [{
|
||||||
|
name: 'vite-like-root-resolve',
|
||||||
|
setup (b) {
|
||||||
|
b.onResolve(
|
||||||
|
{ filter: new RegExp(/^\//) },
|
||||||
|
args => ({
|
||||||
|
path: resolve(projectRoot, args.path.slice(1))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'sw-messages',
|
||||||
|
setup (b) {
|
||||||
|
b.onResolve(
|
||||||
|
{ filter: new RegExp('^' + swMessagesName + '$') },
|
||||||
|
args => ({
|
||||||
|
path: args.path,
|
||||||
|
namespace: 'sw-messages'
|
||||||
|
}))
|
||||||
|
b.onLoad(
|
||||||
|
{ filter: /.*/, namespace: 'sw-messages' },
|
||||||
|
async () => ({
|
||||||
|
contents: await getSWMessagesAsText()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'sw-env',
|
||||||
|
setup (b) {
|
||||||
|
b.onResolve(
|
||||||
|
{ filter: new RegExp('^' + swEnvName + '$') },
|
||||||
|
args => ({
|
||||||
|
path: args.path,
|
||||||
|
namespace: 'sw-env'
|
||||||
|
}))
|
||||||
|
b.onLoad(
|
||||||
|
{ filter: /.*/, namespace: 'sw-env' },
|
||||||
|
() => ({
|
||||||
|
contents: getDevSwEnv()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
const text = res.outputFiles[0].text
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Idea taken from
|
// Idea taken from
|
||||||
// https://github.com/vite-pwa/vite-plugin-pwa/blob/main/src/plugins/build.ts
|
// https://github.com/vite-pwa/vite-plugin-pwa/blob/main/src/plugins/build.ts
|
||||||
// rollup does not support compiling to iife if we want to code-split;
|
// rollup does not support compiling to iife if we want to code-split;
|
||||||
// however, we must compile the service worker to iife because of browser support.
|
// however, we must compile the service worker to iife because of browser support.
|
||||||
// Run another vite build just for the service worker targeting iife at
|
// Run another vite build just for the service worker targeting iife at
|
||||||
// the end of the build.
|
// the end of the build.
|
||||||
export const buildSwPlugin = ({ swSrc, swDest }) => {
|
export const buildSwPlugin = ({
|
||||||
const swFullSrc = resolve(projectRoot, swSrc)
|
swSrc,
|
||||||
const swEnvName = 'virtual:pleroma-fe/service_worker_env'
|
swDest,
|
||||||
const swEnvNameResolved = '\0' + swEnvName
|
}) => {
|
||||||
|
|
||||||
let config
|
let config
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'build-sw-plugin',
|
name: 'build-sw-plugin',
|
||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
configResolved(resolvedConfig) {
|
apply: 'build',
|
||||||
|
configResolved (resolvedConfig) {
|
||||||
config = {
|
config = {
|
||||||
define: resolvedConfig.define,
|
define: resolvedConfig.define,
|
||||||
resolve: resolvedConfig.resolve,
|
resolve: resolvedConfig.resolve,
|
||||||
|
|
@ -38,89 +135,53 @@ export const buildSwPlugin = ({ swSrc, swDest }) => {
|
||||||
publicDir: false,
|
publicDir: false,
|
||||||
build: {
|
build: {
|
||||||
...resolvedConfig.build,
|
...resolvedConfig.build,
|
||||||
emptyOutDir: false,
|
lib: {
|
||||||
rolldownOptions: {
|
entry: swSrc,
|
||||||
input: {
|
formats: ['iife'],
|
||||||
main: swSrc,
|
name: 'sw_pleroma'
|
||||||
},
|
|
||||||
context: 'self',
|
|
||||||
output: {
|
|
||||||
entryFileNames: swDest,
|
|
||||||
codeSplitting: false,
|
|
||||||
format: 'iife',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
emptyOutDir: false,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
entryFileNames: swDest
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
configFile: false,
|
configFile: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
generateBundle: {
|
generateBundle: {
|
||||||
order: 'post',
|
order: 'post',
|
||||||
sequential: true,
|
sequential: true,
|
||||||
async handler(_, bundle) {
|
async handler (_, bundle) {
|
||||||
const assets = Object.keys(bundle)
|
const assets = Object.keys(bundle)
|
||||||
.filter((name) => !name.endsWith('.map'))
|
.filter(name => !/\.map$/.test(name))
|
||||||
.map((name) => '/' + name)
|
.map(name => '/' + name)
|
||||||
|
|
||||||
config.plugins.push({
|
config.plugins.push({
|
||||||
name: 'build-sw-env-plugin',
|
name: 'build-sw-env-plugin',
|
||||||
mode: 'production',
|
resolveId (id) {
|
||||||
resolveId: {
|
if (id === swEnvName) {
|
||||||
filter: { id: exactRegex(swEnvName) },
|
return swEnvNameResolved
|
||||||
handler: () => swEnvNameResolved,
|
}
|
||||||
},
|
return null
|
||||||
load: {
|
|
||||||
filter: { id: exactRegex(swEnvNameResolved) },
|
|
||||||
handler() {
|
|
||||||
return `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
load (id) {
|
||||||
|
if (id === swEnvNameResolved) {
|
||||||
|
return getProdSwEnv({ assets })
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
},
|
|
||||||
resolveId: {
|
|
||||||
filter: { id: new RegExp(swDest) },
|
|
||||||
handler() {
|
|
||||||
return swFullSrc
|
|
||||||
},
|
|
||||||
},
|
|
||||||
load: {
|
|
||||||
filter: { id: new RegExp(swFullSrc) },
|
|
||||||
async handler() {
|
|
||||||
config.plugins.push({
|
|
||||||
name: 'dummy-sw-env',
|
|
||||||
mode: 'development',
|
|
||||||
resolveId: {
|
|
||||||
filter: { id: exactRegex(swEnvName) },
|
|
||||||
handler: () => swEnvNameResolved,
|
|
||||||
},
|
|
||||||
load: {
|
|
||||||
filter: { id: exactRegex(swEnvNameResolved) },
|
|
||||||
handler: () => 'self.serviceWorkerOption = { assets: [] }',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const swBundle = await build(config)
|
|
||||||
return swBundle.output[0]
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error building ServiceWorker:', e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
closeBundle: {
|
closeBundle: {
|
||||||
order: 'post',
|
order: 'post',
|
||||||
sequential: true,
|
sequential: true,
|
||||||
async handler() {
|
async handler () {
|
||||||
if (process.env.VITEST) return
|
console.log('Building service worker for production')
|
||||||
console.info('Building service worker for production')
|
await build(config)
|
||||||
try {
|
}
|
||||||
await build(config)
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error('Error building ServiceWorker:', e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,9 +191,9 @@ const swMessagesNameResolved = '\0' + swMessagesName
|
||||||
export const swMessagesPlugin = () => {
|
export const swMessagesPlugin = () => {
|
||||||
return {
|
return {
|
||||||
name: 'sw-messages-plugin',
|
name: 'sw-messages-plugin',
|
||||||
resolveId(id) {
|
resolveId (id) {
|
||||||
if (id === swMessagesName) {
|
if (id === swMessagesName) {
|
||||||
Object.values(i18nFiles).forEach((f) => {
|
Object.values(i18nFiles).forEach(f => {
|
||||||
this.addWatchFile(f)
|
this.addWatchFile(f)
|
||||||
})
|
})
|
||||||
return swMessagesNameResolved
|
return swMessagesNameResolved
|
||||||
|
|
@ -140,11 +201,11 @@ export const swMessagesPlugin = () => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async load(id) {
|
async load (id) {
|
||||||
if (id === swMessagesNameResolved) {
|
if (id === swMessagesNameResolved) {
|
||||||
return await getSWMessagesAsText()
|
return await getSWMessagesAsText()
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,23 @@
|
||||||
import fs from 'node:fs'
|
|
||||||
import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with {
|
|
||||||
type: 'json',
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(emojis).forEach((k) => {
|
import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json' }
|
||||||
emojis[k].forEach((e) => {
|
import fs from 'fs'
|
||||||
delete e.unicode_version
|
|
||||||
delete e.emoji_version
|
Object.keys(emojis)
|
||||||
delete e.skin_tone_support_unicode_version
|
.map(k => {
|
||||||
|
emojis[k].map(e => {
|
||||||
|
delete e.unicode_version
|
||||||
|
delete e.emoji_version
|
||||||
|
delete e.skin_tone_support_unicode_version
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const res = {}
|
const res = {}
|
||||||
Object.keys(emojis).forEach((k) => {
|
Object.keys(emojis)
|
||||||
const groupId = k.replace('&', 'and').replaceAll(' ', '-').toLowerCase()
|
.map(k => {
|
||||||
res[groupId] = emojis[k]
|
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
|
||||||
})
|
res[groupId] = emojis[k]
|
||||||
|
})
|
||||||
|
|
||||||
console.info('Updating emojis...')
|
console.info('Updating emojis...')
|
||||||
try {
|
fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res))
|
||||||
fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res))
|
console.info('Done.')
|
||||||
console.info('Done.')
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed updating emoji', e)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
Fixed crashing on Firefox 153
|
|
||||||
Fixed serif-fonts being used by default in some cases
|
|
||||||
Fixed font select component showing and using objects instead of strings
|
|
||||||
Fixed generic ('sans-serif', 'monospace' etc) fonts not working
|
|
||||||
1
changelog.d/action-button-extra-counter.add
Normal file
1
changelog.d/action-button-extra-counter.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Display counter for status action buttons when they are on the menu
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
fixed tapping "Mute..." and "Change visiblity" (admin action) in extra status actions closing the dropdown
|
|
||||||
1
changelog.d/akkoma-sharkey-net-support.add
Normal file
1
changelog.d/akkoma-sharkey-net-support.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Added support for Akkoma and IceShrimp.NET backend
|
||||||
2
changelog.d/arithmetic-blend.add
Normal file
2
changelog.d/arithmetic-blend.add
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add arithmetic blend ISS function
|
||||||
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
fix image cropper error preventing avatar upload
|
|
||||||
1
changelog.d/better-scroll-button.add
Normal file
1
changelog.d/better-scroll-button.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for detachable scrollTop button
|
||||||
1
changelog.d/bookmark-button-align.fix
Normal file
1
changelog.d/bookmark-button-align.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix bookmark button alignment in the extra actions menu
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Chat view for threads
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
fix pinned indicator looking wrong on CJK locales
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix legacy settings migration being discarded after it ran.
|
|
||||||
1
changelog.d/csp.add
Normal file
1
changelog.d/csp.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Compatibility with stricter CSP (Akkoma backend)
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fixed drafts creating duplicates on edit instead of updating
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix style of extra notifications
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix error dialog when posting in chat
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
domain mute fixedw
|
|
||||||
0
changelog.d/fix-wrap.skip
Normal file
0
changelog.d/fix-wrap.skip
Normal file
|
|
@ -1 +0,0 @@
|
||||||
Fix follower remove API call
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix followers list showing followed users as non-followed
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Prevent service worker from caching media-proxy responses
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix MFA login and recovery code authentication.
|
|
||||||
0
changelog.d/migrate-auth-flow-pinia.skip
Normal file
0
changelog.d/migrate-auth-flow-pinia.skip
Normal file
|
|
@ -1 +0,0 @@
|
||||||
fix moderation tools button possibly not appearing for admins
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fixed moderation actions requiring confirmation closing user popover
|
|
||||||
1
changelog.d/mutes-sync.add
Normal file
1
changelog.d/mutes-sync.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Synchronized mutes, advanced mute control (regexp, expiry, naming)
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Do not crash even on css rule insertion failure
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Building PleromaFE now requires NodeJS version 24 or newer
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix posting not working when using preview
|
|
||||||
1
changelog.d/profile-error.fix
Normal file
1
changelog.d/profile-error.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix error styling for user profiles
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fixed server-side domain mutes rendering/api calls
|
|
||||||
0
changelog.d/small-fixes.skip
Normal file
0
changelog.d/small-fixes.skip
Normal file
|
|
@ -1 +0,0 @@
|
||||||
More timelines now can utilize WebSocket streaming
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
switches between streaming and polling should be more reliable now
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
Added an indicator next to instance's name showing WebSocket connection status (if enabled).
|
|
||||||
Timeline no longer show "loading" indicator at the bottom when fetching newer posts.
|
|
||||||
Added small indicator on top of timeline when new posts are being fetched
|
|
||||||
1
changelog.d/sw-cache-assets.add
Normal file
1
changelog.d/sw-cache-assets.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Cache assets and emojis with service worker
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix theme lists failing to load when custom resource indexes are unavailable
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix Theme 2 fonts falling back to serif after upgrade
|
|
||||||
1
changelog.d/theme3-body-class.add
Normal file
1
changelog.d/theme3-body-class.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Indicate currently active V3 theme as a body element class
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix Themes 2.0 applying incorrect fonts
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fixed themes 3 not loading in appearance tab
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fixed layout selector (third column/reverse) not working immideately upon change
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fixed status index approxmiation in timeline rendering for dynamically changing viewport geometries
|
|
||||||
1
changelog.d/unify-show-hide-buttons.add
Normal file
1
changelog.d/unify-show-hide-buttons.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Unify show/hide content buttons
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Implemented new virtual scrolling. Opening huge threads should be faster now
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
services:
|
|
||||||
db:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: pleroma
|
|
||||||
POSTGRES_PASSWORD: pleroma
|
|
||||||
POSTGRES_DB: pleroma
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U pleroma -d pleroma"]
|
|
||||||
interval: 2s
|
|
||||||
timeout: 2s
|
|
||||||
retries: 30
|
|
||||||
|
|
||||||
pleroma:
|
|
||||||
image: ${PLEROMA_IMAGE:-git.pleroma.social/pleroma/pleroma:stable}
|
|
||||||
environment:
|
|
||||||
DB_USER: pleroma
|
|
||||||
DB_PASS: pleroma
|
|
||||||
DB_NAME: pleroma
|
|
||||||
DB_HOST: db
|
|
||||||
DB_PORT: 5432
|
|
||||||
DOMAIN: localhost
|
|
||||||
INSTANCE_NAME: Pleroma E2E
|
|
||||||
ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
|
|
||||||
NOTIFY_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
|
|
||||||
E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
|
|
||||||
E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
|
|
||||||
E2E_ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
|
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
volumes:
|
|
||||||
- ./docker/pleroma/entrypoint.e2e.sh:/opt/pleroma/entrypoint.e2e.sh:ro
|
|
||||||
entrypoint: ["/bin/ash", "/opt/pleroma/entrypoint.e2e.sh"]
|
|
||||||
healthcheck:
|
|
||||||
# NOTE: "localhost" may resolve to ::1 in some images (IPv6) while Pleroma only
|
|
||||||
# listens on IPv4 in this container. Use 127.0.0.1 to avoid false negatives.
|
|
||||||
test: ["CMD-SHELL", "test -f /var/lib/pleroma/.e2e_seeded && wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null || exit 1"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 60
|
|
||||||
ports:
|
|
||||||
- 4000:4000
|
|
||||||
|
|
||||||
e2e:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: docker/e2e/Dockerfile.e2e
|
|
||||||
depends_on:
|
|
||||||
pleroma:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
CI: "1"
|
|
||||||
VITE_PROXY_TARGET: http://pleroma:4000
|
|
||||||
VITE_PROXY_ORIGIN: http://localhost:4000
|
|
||||||
E2E_BASE_URL: http://localhost:8099
|
|
||||||
E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
|
|
||||||
E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
|
|
||||||
command: ["yarn", "e2e:pw"]
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
FROM mcr.microsoft.com/playwright:v1.61.0-jammy
|
|
||||||
|
|
||||||
RUN npm install -g yarn@1.22.22
|
|
||||||
|
|
||||||
RUN adduser --system playwright --group --shell=/bin/bash
|
|
||||||
|
|
||||||
USER playwright
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
|
||||||
|
|
||||||
COPY package.json yarn.lock ./
|
|
||||||
RUN yarn --frozen-lockfile
|
|
||||||
|
|
||||||
COPY --chown=playwright ./build ./src ./test ./public ./static ./yarn.lock ./package.json .
|
|
||||||
|
|
||||||
ENV CI=1
|
|
||||||
|
|
||||||
CMD ["yarn", "e2e:pw"]
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
#!/bin/ash
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
SEED_SENTINEL_PATH="/var/lib/pleroma/.e2e_seeded"
|
|
||||||
CONFIG_OVERRIDE_PATH="/var/lib/pleroma/config.exs"
|
|
||||||
|
|
||||||
echo "-- Waiting for database..."
|
|
||||||
while ! pg_isready -U "${DB_USER:-pleroma}" -d "postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma}" -t 1; do
|
|
||||||
sleep 1s
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "-- Writing E2E config overrides..."
|
|
||||||
cat > "$CONFIG_OVERRIDE_PATH" <<'EOF'
|
|
||||||
import Config
|
|
||||||
|
|
||||||
config :pleroma, Pleroma.Captcha,
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
config :pleroma, :instance,
|
|
||||||
registrations_open: true,
|
|
||||||
account_activation_required: false,
|
|
||||||
approval_required: false
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "-- Running migrations..."
|
|
||||||
/opt/pleroma/bin/pleroma_ctl migrate
|
|
||||||
|
|
||||||
echo "-- Starting!"
|
|
||||||
/opt/pleroma/bin/pleroma start &
|
|
||||||
PLEROMA_PID="$!"
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
if [ -n "${PLEROMA_PID:-}" ] && kill -0 "$PLEROMA_PID" 2>/dev/null; then
|
|
||||||
kill -TERM "$PLEROMA_PID"
|
|
||||||
wait "$PLEROMA_PID" || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup INT TERM
|
|
||||||
|
|
||||||
echo "-- Waiting for API..."
|
|
||||||
api_ok="false"
|
|
||||||
for _i in $(seq 1 120); do
|
|
||||||
if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
|
|
||||||
api_ok="true"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1s
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$api_ok" != "true" ]; then
|
|
||||||
echo "Timed out waiting for Pleroma API to become available"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "$SEED_SENTINEL_PATH" ]; then
|
|
||||||
if [ -n "${E2E_ADMIN_USERNAME:-}" ] && [ -n "${E2E_ADMIN_PASSWORD:-}" ] && [ -n "${E2E_ADMIN_EMAIL:-}" ]; then
|
|
||||||
echo "-- Seeding admin user (${E2E_ADMIN_USERNAME})..."
|
|
||||||
if ! /opt/pleroma/bin/pleroma_ctl user new "$E2E_ADMIN_USERNAME" "$E2E_ADMIN_EMAIL" --admin --password "$E2E_ADMIN_PASSWORD" -y; then
|
|
||||||
echo "-- User already exists (or creation failed), ensuring admin + confirmed..."
|
|
||||||
/opt/pleroma/bin/pleroma_ctl user set "$E2E_ADMIN_USERNAME" --admin --confirmed
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "-- Skipping admin seeding (missing E2E_ADMIN_* env)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
touch "$SEED_SENTINEL_PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
wait "$PLEROMA_PID"
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
|
|
||||||
PleromaFE gets its configuration from several sources, in order of preference (the one above overrides ones below it)
|
PleromaFE gets its configuration from several sources, in order of preference (the one above overrides ones below it)
|
||||||
|
|
||||||
1. `/api/pleroma/frontend_configurations` - this is generated by backend and includes FE/Client-specific data. PleromaFE uses the `pleroma_fe` field of it. For more info on changing config on BE, look [here](../backend/configuration/cheatsheet.md#frontend_configurations)
|
1. `/api/statusnet/config.json` - this is generated on Backend and contains multiple things including instance name, char limit etc. It also contains FE/Client-specific data, PleromaFE uses `pleromafe` field of it. For more info on changing config on BE, look [here](../backend/configuration/cheatsheet.md#frontend_configurations)
|
||||||
2. `/static/config.json` - this is a static FE-provided file, containing only FE specific configuration. This file is completely optional and could be removed but is useful as a fallback if some configuration JSON property isn't present in BE-provided config. It's also a reference point to check what default configuration are and what JSON properties even exist. In local dev mode it could be used to override BE configuration, more about that in HACKING.md. File is located [here](https://git.pleroma.social/pleroma/pleroma-fe/src/public/static/config.json).
|
2. `/static/config.json` - this is a static FE-provided file, containing only FE specific configuration. This file is completely optional and could be removed but is useful as a fallback if some configuration JSON property isn't present in BE-provided config. It's also a reference point to check what default configuration are and what JSON properties even exist. In local dev mode it could be used to override BE configuration, more about that in HACKING.md. File is located [here](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/static/config.json).
|
||||||
3. Built-in defaults. Those are hard-coded defaults that are used when `/static/config.json` is not available and BE-provided configuration JSON is missing some JSON properties. ( [Code](https://git.pleroma.social/pleroma/pleroma-fe/src/src/stores/instance.js) )
|
3. Built-in defaults. Those are hard-coded defaults that are used when `/static/config.json` is not available and BE-provided configuration JSON is missing some JSON properties. ( [Code](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/modules/instance.js) )
|
||||||
|
|
||||||
## Instance-defaults
|
## Instance-defaults
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,9 @@ server {
|
||||||
|
|
||||||
In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with [Pleroma Extensions](../backend/API/differences_in_mastoapi_responses.md) to fetch the data. The rest is either QvitterAPI leftovers or pleroma-exclusive APIs. QvitterAPI doesn't exactly have documentation and uses different JSON structure and sometimes different parameters and workflows, [this](https://twitter-api.readthedocs.io/en/latest/index.html) could be a good reference though. Some pleroma-exclusive API may still be using QvitterAPI JSON structure.
|
In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with [Pleroma Extensions](../backend/API/differences_in_mastoapi_responses.md) to fetch the data. The rest is either QvitterAPI leftovers or pleroma-exclusive APIs. QvitterAPI doesn't exactly have documentation and uses different JSON structure and sometimes different parameters and workflows, [this](https://twitter-api.readthedocs.io/en/latest/index.html) could be a good reference though. Some pleroma-exclusive API may still be using QvitterAPI JSON structure.
|
||||||
|
|
||||||
PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/src/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.
|
PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.
|
||||||
|
|
||||||
For most part, PleromaFE tries to store all the info it can get in global pinia store - every user and post are passed trough updating mechanism where data is either added or merged with existing data, reactively updating the information throughout UI, so if in newest request user's post counter increased, it will be instantly updated in open user profile cards. This is also used to find users, posts and sometimes to build timelines and/or request parameters.
|
For most part, PleromaFE tries to store all the info it can get in global vuex store - every user and post are passed trough updating mechanism where data is either added or merged with existing data, reactively updating the information throughout UI, so if in newest request user's post counter increased, it will be instantly updated in open user profile cards. This is also used to find users, posts and sometimes to build timelines and/or request parameters.
|
||||||
|
|
||||||
PleromaFE also tries to persist this store, however only stable data is stored, such as user authentication and preferences, user highlights. Persistence is performed by saving and loading chunk of vuex store in browser's LocalStorage/IndexedDB.
|
PleromaFE also tries to persist this store, however only stable data is stored, such as user authentication and preferences, user highlights. Persistence is performed by saving and loading chunk of vuex store in browser's LocalStorage/IndexedDB.
|
||||||
|
|
||||||
|
|
|
||||||
37
eslint.config.mjs
Normal file
37
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import vue from "eslint-plugin-vue";
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import globals from "globals";
|
||||||
|
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...vue.configs['flat/recommended'],
|
||||||
|
js.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ["**/*.js", "**/*.mjs", "**/*.vue"],
|
||||||
|
ignores: ["build/*.js", "config/*.js"],
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2024,
|
||||||
|
sourceType: "module",
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
parser: "@babel/eslint-parser",
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.vitest,
|
||||||
|
...globals.chai,
|
||||||
|
...globals.commonjs,
|
||||||
|
...globals.serviceworker
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'arrow-parens': 0,
|
||||||
|
'generator-star-spacing': 0,
|
||||||
|
'no-debugger': 0,
|
||||||
|
'vue/require-prop-types': 0,
|
||||||
|
'vue/multi-word-component-names': 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -11,12 +11,14 @@
|
||||||
<link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" />
|
<link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" />
|
||||||
<!-- putting styles here to avoid having to wait for styles to load up -->
|
<!-- putting styles here to avoid having to wait for styles to load up -->
|
||||||
<link rel="stylesheet" id="splashscreen" href="/static/splash.css" />
|
<link rel="stylesheet" id="splashscreen" href="/static/splash.css" />
|
||||||
<link rel="stylesheet" id="custom-styles-holder" type="text/css" href="/static/empty.css" />
|
<link rel="stylesheet" id="pleroma-eager-styles" type="text/css" href="/static/empty.css" />
|
||||||
|
<link rel="stylesheet" id="pleroma-lazy-styles" type="text/css" href="/static/empty.css" />
|
||||||
|
<link rel="stylesheet" id="theme-holder" type="text/css" href="/static/empty.css" />
|
||||||
<!--server-generated-meta-->
|
<!--server-generated-meta-->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
||||||
<div id="splash" class="initial-hidden">
|
<div id="splash">
|
||||||
<!-- we are hiding entire graphic so no point showing credit -->
|
<!-- we are hiding entire graphic so no point showing credit -->
|
||||||
<div aria-hidden="true" id="splash-credit">
|
<div aria-hidden="true" id="splash-credit">
|
||||||
Art by pipivovott
|
Art by pipivovott
|
||||||
|
|
@ -31,7 +33,7 @@
|
||||||
<div class="chunk" id="chunk-E">
|
<div class="chunk" id="chunk-E">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img id="mascot" alt="A cute mascot" src="/static/pleromatan_apology_small.webp">
|
<img id="mascot" src="/static/pleromatan_apology_small.webp">
|
||||||
</div>
|
</div>
|
||||||
<div id="status" class="css-ok">
|
<div id="status" class="css-ok">
|
||||||
<!-- (。>﹏<) -->
|
<!-- (。>﹏<) -->
|
||||||
|
|
|
||||||
150
package.json
150
package.json
|
|
@ -1,94 +1,122 @@
|
||||||
{
|
{
|
||||||
"name": "pleroma_fe",
|
"name": "pleroma_fe",
|
||||||
"version": "2.11.1",
|
"version": "2.7.1",
|
||||||
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
||||||
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/src/CONTRIBUTORS.md>",
|
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node build/update-emoji.js && vite dev",
|
"dev": "node build/update-emoji.js && vite dev",
|
||||||
"build": "node build/update-emoji.js && vite build",
|
"build": "node build/update-emoji.js && vite build",
|
||||||
"unit": "node build/update-emoji.js && vitest --run --coverage",
|
"unit": "node build/update-emoji.js && vitest --run",
|
||||||
"unit-ci": "node build/update-emoji.js && vitest --run --browser.headless",
|
"unit-ci": "node build/update-emoji.js && vitest --run --browser.headless",
|
||||||
"unit-ci-coverage": "node build/update-emoji.js && vitest --run --coverage --browser.headless",
|
"unit:watch": "node build/update-emoji.js && vitest",
|
||||||
"unit:watch": "node build/update-emoji.js && vitest --coverage",
|
"e2e": "node test/e2e/runner.js",
|
||||||
"e2e:pw": "playwright test --config test/e2e-playwright/playwright.config.mjs",
|
|
||||||
"e2e": "sh ./tools/e2e/run.sh",
|
|
||||||
"test": "yarn run unit && yarn run e2e",
|
"test": "yarn run unit && yarn run e2e",
|
||||||
"ci-biome": "yarn exec biome check",
|
"stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
|
||||||
"ci-stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
|
"lint": "eslint src test/unit/specs test/e2e/specs",
|
||||||
"lint": "yarn ci-biome; yarn ci-stylelint",
|
"lint-fix": "eslint --fix src test/unit/specs test/e2e/specs"
|
||||||
"lint-fix": "yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/runtime": "7.27.1",
|
||||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "7.3.1",
|
"@fortawesome/fontawesome-svg-core": "6.7.2",
|
||||||
"@fortawesome/free-regular-svg-icons": "7.3.1",
|
"@fortawesome/free-regular-svg-icons": "6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "7.3.1",
|
"@fortawesome/free-solid-svg-icons": "6.7.2",
|
||||||
"@fortawesome/vue-fontawesome": "3.3.3",
|
"@fortawesome/vue-fontawesome": "3.0.8",
|
||||||
"@kazvmoe-infra/pinch-zoom-element": "1.3.0",
|
"@floatingghost/pinch-zoom-element": "1.3.1",
|
||||||
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
||||||
"@ruffle-rs/ruffle": "0.6.0-nightly.2026.9.4",
|
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
|
||||||
"@vuelidate/core": "2.0.3",
|
"@vuelidate/core": "2.0.3",
|
||||||
"@vuelidate/validators": "2.0.4",
|
"@vuelidate/validators": "2.0.4",
|
||||||
"@web3-storage/parse-link-header": "3.1.0",
|
"@web3-storage/parse-link-header": "^3.1.0",
|
||||||
"body-scroll-lock": "3.1.5",
|
"body-scroll-lock": "3.1.5",
|
||||||
"chromatism": "3.0.0",
|
"chromatism": "3.0.0",
|
||||||
"click-outside-vue3": "4.0.1",
|
"click-outside-vue3": "4.0.1",
|
||||||
"cropperjs": "2.2.0",
|
"cropperjs": "2.0.0",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"hash-sum": "2.0.0",
|
"globals": "^16.0.0",
|
||||||
"js-cookie": "3.0.8",
|
"hash-sum": "^2.0.0",
|
||||||
|
"js-cookie": "3.0.5",
|
||||||
"localforage": "1.10.0",
|
"localforage": "1.10.0",
|
||||||
"lodash-es": "4.17.21",
|
"parse-link-header": "2.0.0",
|
||||||
"phoenix": "1.8.1",
|
"phoenix": "1.7.21",
|
||||||
"pinia": "4.0.3",
|
"pinia": "^3.0.0",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
|
"querystring-es3": "0.2.1",
|
||||||
|
"url": "0.11.4",
|
||||||
"utf8": "3.0.0",
|
"utf8": "3.0.0",
|
||||||
"uuid": "14.0.2",
|
"uuid": "11.1.0",
|
||||||
"vue": "3.5.42",
|
"vue": "3.5.17",
|
||||||
"vue-i18n": "11.4.10",
|
"vue-i18n": "11",
|
||||||
"vue-router": "5.3.1",
|
"vue-router": "4.5.1",
|
||||||
"vue-virtual-scroller": "3.0.5"
|
"vue-virtual-scroller": "^2.0.0-beta.7",
|
||||||
|
"vuex": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.5.11",
|
"@babel/core": "7.27.1",
|
||||||
"@pinia/testing": "2.0.1",
|
"@babel/eslint-parser": "7.27.1",
|
||||||
|
"@babel/plugin-transform-runtime": "7.27.1",
|
||||||
|
"@babel/preset-env": "7.27.2",
|
||||||
|
"@babel/register": "7.27.1",
|
||||||
"@ungap/event-target": "0.2.4",
|
"@ungap/event-target": "0.2.4",
|
||||||
"@vitejs/devtools": "0.7.2",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vitejs/plugin-vue": "6.0.8",
|
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||||
"@vitejs/plugin-vue-jsx": "5.1.6",
|
"@vitest/browser": "^3.0.7",
|
||||||
"@vitest/browser": "5.0.0",
|
"@vitest/ui": "^3.0.7",
|
||||||
"@vitest/browser-playwright": "5.0.0",
|
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||||
"@vitest/coverage-v8": "5.0.0",
|
"@vue/babel-plugin-jsx": "1.4.0",
|
||||||
"@vitest/ui": "5.0.0",
|
"@vue/compiler-sfc": "3.5.17",
|
||||||
"@vue/compiler-sfc": "3.5.42",
|
"@vue/test-utils": "2.4.6",
|
||||||
"@vue/compiler-dom": "3.5.42",
|
"autoprefixer": "10.4.21",
|
||||||
"@vue/devtools-api": "8.2.0",
|
"babel-plugin-lodash": "3.3.4",
|
||||||
"@vue/test-utils": "2.5.0",
|
"chai": "5.2.0",
|
||||||
"autoprefixer": "10.5.4",
|
"chalk": "5.4.1",
|
||||||
"iso-639-1": "3.1.6",
|
"chromedriver": "135.0.4",
|
||||||
"playwright": "1.61.0",
|
"connect-history-api-fallback": "2.0.0",
|
||||||
"postcss": "8.5.28",
|
"cross-spawn": "7.0.6",
|
||||||
"postcss-html": "2.0.0",
|
"custom-event-polyfill": "1.0.7",
|
||||||
"postcss-scss": "4.0.9",
|
"eslint": "9.26.0",
|
||||||
"sass-embedded": "1.104.0",
|
"vue-eslint-parser": "10.1.3",
|
||||||
|
"eslint-config-standard": "17.1.0",
|
||||||
|
"eslint-formatter-friendly": "7.0.0",
|
||||||
|
"eslint-plugin-import": "2.31.0",
|
||||||
|
"eslint-plugin-n": "17.18.0",
|
||||||
|
"eslint-plugin-promise": "7.2.1",
|
||||||
|
"eslint-plugin-vue": "10.1.0",
|
||||||
|
"eventsource-polyfill": "0.9.6",
|
||||||
|
"express": "5.1.0",
|
||||||
|
"function-bind": "1.1.2",
|
||||||
|
"http-proxy-middleware": "3.0.5",
|
||||||
|
"iso-639-1": "3.1.5",
|
||||||
|
"lodash": "4.17.21",
|
||||||
|
"msw": "2.10.2",
|
||||||
|
"nightwatch": "3.12.1",
|
||||||
|
"playwright": "1.52.0",
|
||||||
|
"postcss": "8.5.3",
|
||||||
|
"postcss-html": "^1.5.0",
|
||||||
|
"postcss-scss": "^4.0.6",
|
||||||
|
"sass": "1.89.2",
|
||||||
|
"selenium-server": "3.141.59",
|
||||||
|
"semver": "7.7.2",
|
||||||
"serve-static": "2.2.0",
|
"serve-static": "2.2.0",
|
||||||
"stylelint": "17.14.0",
|
"shelljs": "0.10.0",
|
||||||
"stylelint-config-html": "2.0.0",
|
"sinon": "20.0.0",
|
||||||
"stylelint-config-recommended": "18.0.0",
|
"sinon-chai": "4.0.0",
|
||||||
"stylelint-config-recommended-scss": "17.0.1",
|
"stylelint": "16.19.1",
|
||||||
"stylelint-config-recommended-vue": "2.0.0",
|
"stylelint-config-html": "^1.1.0",
|
||||||
"stylelint-config-standard": "40.0.0",
|
"stylelint-config-recommended": "^16.0.0",
|
||||||
"vite": "8.2.2",
|
"stylelint-config-recommended-scss": "^14.0.0",
|
||||||
"vite-plugin-biome": "^1.2.0",
|
"stylelint-config-recommended-vue": "^1.6.0",
|
||||||
"vite-plugin-stylelint": "6.3.0",
|
"stylelint-config-standard": "38.0.0",
|
||||||
"vite-plugin-vue-devtools": "8.2.1",
|
"vite": "^6.1.0",
|
||||||
"vitest": "5.0.0"
|
"vite-plugin-eslint2": "^5.0.3",
|
||||||
|
"vite-plugin-stylelint": "^6.0.0",
|
||||||
|
"vitest": "^3.0.7"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 24.0.0"
|
"node": ">= 16.0.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import autoprefixer from 'autoprefixer'
|
import autoprefixer from 'autoprefixer'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
plugins: [autoprefixer],
|
plugins: [
|
||||||
|
autoprefixer
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
public/static/empty.css
Normal file
1
public/static/empty.css
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
// nothing here //
|
||||||
|
|
@ -1,26 +1,6 @@
|
||||||
{
|
{
|
||||||
"pleroma-dark": [
|
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||||
"Pleroma Dark",
|
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||||
"#121a24",
|
|
||||||
"#182230",
|
|
||||||
"#b9b9ba",
|
|
||||||
"#d8a070",
|
|
||||||
"#d31014",
|
|
||||||
"#0fa00f",
|
|
||||||
"#0095ff",
|
|
||||||
"#ffa500"
|
|
||||||
],
|
|
||||||
"pleroma-light": [
|
|
||||||
"Pleroma Light",
|
|
||||||
"#f2f4f6",
|
|
||||||
"#dbe0e8",
|
|
||||||
"#304055",
|
|
||||||
"#f86f0f",
|
|
||||||
"#d31014",
|
|
||||||
"#0fa00f",
|
|
||||||
"#0095ff",
|
|
||||||
"#ffa500"
|
|
||||||
],
|
|
||||||
"classic-dark": {
|
"classic-dark": {
|
||||||
"name": "Classic Dark",
|
"name": "Classic Dark",
|
||||||
"bg": "#161c20",
|
"bg": "#161c20",
|
||||||
|
|
@ -32,28 +12,8 @@
|
||||||
"cBlue": "#0095ff",
|
"cBlue": "#0095ff",
|
||||||
"cOrange": "#ffa500"
|
"cOrange": "#ffa500"
|
||||||
},
|
},
|
||||||
"bird": [
|
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
|
||||||
"Bird",
|
"pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
|
||||||
"#f8fafd",
|
|
||||||
"#e6ecf0",
|
|
||||||
"#14171a",
|
|
||||||
"#0084b8",
|
|
||||||
"#e0245e",
|
|
||||||
"#17bf63",
|
|
||||||
"#1b95e0",
|
|
||||||
"#fab81e"
|
|
||||||
],
|
|
||||||
"pleroma-amoled": [
|
|
||||||
"Pleroma Dark AMOLED",
|
|
||||||
"#000000",
|
|
||||||
"#111111",
|
|
||||||
"#b0b0b1",
|
|
||||||
"#d8a070",
|
|
||||||
"#aa0000",
|
|
||||||
"#0fa00f",
|
|
||||||
"#0095ff",
|
|
||||||
"#d59500"
|
|
||||||
],
|
|
||||||
"tomorrow-night": {
|
"tomorrow-night": {
|
||||||
"name": "Tomorrow Night",
|
"name": "Tomorrow Night",
|
||||||
"bg": "#1d1f21",
|
"bg": "#1d1f21",
|
||||||
|
|
@ -76,28 +36,8 @@
|
||||||
"cGreen": "#50FA7B",
|
"cGreen": "#50FA7B",
|
||||||
"cOrange": "#FFB86C"
|
"cOrange": "#FFB86C"
|
||||||
},
|
},
|
||||||
"ir-black": [
|
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
|
||||||
"Ir Black",
|
"monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
|
||||||
"#000000",
|
|
||||||
"#242422",
|
|
||||||
"#b5b3aa",
|
|
||||||
"#ff6c60",
|
|
||||||
"#FF6C60",
|
|
||||||
"#A8FF60",
|
|
||||||
"#96CBFE",
|
|
||||||
"#FFFFB6"
|
|
||||||
],
|
|
||||||
"monokai": [
|
|
||||||
"Monokai",
|
|
||||||
"#272822",
|
|
||||||
"#383830",
|
|
||||||
"#f8f8f2",
|
|
||||||
"#f92672",
|
|
||||||
"#F92672",
|
|
||||||
"#a6e22e",
|
|
||||||
"#66d9ef",
|
|
||||||
"#f4bf75"
|
|
||||||
],
|
|
||||||
"purple-stream": {
|
"purple-stream": {
|
||||||
"name": "Purple stream",
|
"name": "Purple stream",
|
||||||
"bg": "#17171A",
|
"bg": "#17171A",
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,15 @@ body {
|
||||||
|
|
||||||
#splash {
|
#splash {
|
||||||
--scale: 1;
|
--scale: 1;
|
||||||
|
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
grid-template-columns: auto;
|
grid-template-columns: auto;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
place-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
justify-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #0f161e;
|
background: #0f161e;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
|
@ -20,20 +21,13 @@ body {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
font-size: calc(1vw + 1vh + 1vmin);
|
font-size: calc(1vw + 1vh + 1vmin);
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 500ms ease-out 2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#splash.hidden,
|
|
||||||
#splash.initial-hidden {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#splash-credit {
|
#splash-credit {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 1em;
|
font-size: 14px;
|
||||||
bottom: 1em;
|
bottom: 16px;
|
||||||
right: 1em;
|
right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#splash-container {
|
#splash-container {
|
||||||
|
|
@ -65,17 +59,16 @@ body {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
grid-template-rows: repeat(8, 1fr);
|
grid-template-rows: repeat(8, 1fr);
|
||||||
grid-template-columns: repeat(5, 1fr);
|
grid-template-columns: repeat(5, 1fr);
|
||||||
grid-template-areas:
|
grid-template-areas: "P P . L L"
|
||||||
"P P . L L"
|
"P P . L L"
|
||||||
"P P . L L"
|
"P P . L L"
|
||||||
"P P . L L"
|
"P P . L L"
|
||||||
"P P . L L"
|
"P P . . ."
|
||||||
"P P . . ."
|
"P P . . ."
|
||||||
"P P . . ."
|
"P P . E E"
|
||||||
"P P . E E"
|
"P P . E E";
|
||||||
"P P . E E";
|
|
||||||
|
|
||||||
--logoChunkSize: calc(2em * 0.5 * var(--scale));
|
--logoChunkSize: calc(2em * 0.5 * var(--scale))
|
||||||
}
|
}
|
||||||
|
|
||||||
.chunk {
|
.chunk {
|
||||||
|
|
@ -85,7 +78,7 @@ body {
|
||||||
|
|
||||||
#chunk-P {
|
#chunk-P {
|
||||||
grid-area: P;
|
grid-area: P;
|
||||||
border-top-left-radius: calc(var(--logoChunkSize) / 2);
|
border-top-left-radius: calc(var(--logoChunkSize) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#chunk-L {
|
#chunk-L {
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@
|
||||||
Pleroma install containing the real ToS for your instance.</p>
|
Pleroma install containing the real ToS for your instance.</p>
|
||||||
<p>See the <a href='https://docs.pleroma.social/backend/configuration/static_dir/'>Pleroma documentation</a> for more information.</p>
|
<p>See the <a href='https://docs.pleroma.social/backend/configuration/static_dir/'>Pleroma documentation</a> for more information.</p>
|
||||||
<br>
|
<br>
|
||||||
<img src="/static/logo.svg" alt="Pleroma Logo" style="display: block; margin: auto; max-width: 100%; height: 50px; object-fit: contain;" />
|
<img src="/static/logo.svg" style="display: block; margin: auto; max-width: 100%; height: 50px; object-fit: contain;" />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": ["config:base"]
|
"extends": [
|
||||||
|
"config:base"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
sonar.projectKey=Pleroma-FE
|
|
||||||
sonar.qualitygatesonar.host.url.wait=true
|
|
||||||
sonar.host.url=https://sonarqube.pleroma.dev
|
|
||||||
sonar.sources=src,build,tools
|
|
||||||
sonar.tests=test
|
|
||||||
303
src/App.js
303
src/App.js
|
|
@ -1,279 +1,187 @@
|
||||||
import { throttle } from 'lodash-es'
|
import UserPanel from './components/user_panel/user_panel.vue'
|
||||||
import { mapState } from 'pinia'
|
import NavPanel from './components/nav_panel/nav_panel.vue'
|
||||||
import { defineAsyncComponent } from 'vue'
|
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
|
||||||
|
import FeaturesPanel from './components/features_panel/features_panel.vue'
|
||||||
import DesktopNav from 'src/components/desktop_nav/desktop_nav.vue'
|
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
|
||||||
import FeaturesPanel from 'src/components/features_panel/features_panel.vue'
|
import ShoutPanel from './components/shout_panel/shout_panel.vue'
|
||||||
import GlobalError from 'src/components/global_error/global_error.vue'
|
import MediaModal from './components/media_modal/media_modal.vue'
|
||||||
import GlobalNoticeList from 'src/components/global_notice_list/global_notice_list.vue'
|
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
||||||
import InstanceSpecificPanel from 'src/components/instance_specific_panel/instance_specific_panel.vue'
|
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
|
||||||
import MobileNav from 'src/components/mobile_nav/mobile_nav.vue'
|
import MobileNav from './components/mobile_nav/mobile_nav.vue'
|
||||||
import MobilePostStatusButton from 'src/components/mobile_post_status_button/mobile_post_status_button.vue'
|
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
|
||||||
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
|
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
|
||||||
import UserPanel from 'src/components/user_panel/user_panel.vue'
|
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
|
||||||
|
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
|
||||||
|
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
|
||||||
|
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
|
||||||
import { getOrCreateServiceWorker } from './services/sw/sw'
|
import { getOrCreateServiceWorker } from './services/sw/sw'
|
||||||
import { windowHeight, windowWidth } from './services/window_utils/window_utils'
|
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
import { useShoutStore } from './stores/shout'
|
||||||
|
import { useInterfaceStore } from './stores/interface'
|
||||||
|
|
||||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
import { throttle } from 'lodash'
|
||||||
import { useI18nStore } from 'src/stores/i18n.js'
|
|
||||||
import { useInstanceStore } from 'src/stores/instance.js'
|
|
||||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
|
||||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
|
||||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
|
||||||
import { useShoutStore } from 'src/stores/shout.js'
|
|
||||||
import { useUsersStore } from 'src/stores/users.js'
|
|
||||||
|
|
||||||
// Helper to unwrap reactive proxies
|
|
||||||
window.toValue = (x) => JSON.parse(JSON.stringify(x))
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {
|
components: {
|
||||||
UserPanel,
|
UserPanel,
|
||||||
NavPanel,
|
NavPanel,
|
||||||
Notifications: defineAsyncComponent(
|
Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
|
||||||
() => import('src/components/notifications/notifications.vue'),
|
|
||||||
),
|
|
||||||
InstanceSpecificPanel,
|
InstanceSpecificPanel,
|
||||||
FeaturesPanel,
|
FeaturesPanel,
|
||||||
WhoToFollowPanel: defineAsyncComponent(
|
WhoToFollowPanel,
|
||||||
() =>
|
ShoutPanel,
|
||||||
import('src/components/who_to_follow_panel/who_to_follow_panel.vue'),
|
MediaModal,
|
||||||
),
|
SideDrawer,
|
||||||
ShoutPanel: defineAsyncComponent(
|
|
||||||
() => import('src/components/shout_panel/shout_panel.vue'),
|
|
||||||
),
|
|
||||||
MediaModal: defineAsyncComponent(
|
|
||||||
() => import('src/components/media_modal/media_modal.vue'),
|
|
||||||
),
|
|
||||||
MobilePostStatusButton,
|
MobilePostStatusButton,
|
||||||
MobileNav,
|
MobileNav,
|
||||||
DesktopNav,
|
DesktopNav,
|
||||||
FollowRequestConfirm: defineAsyncComponent(
|
SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
|
||||||
() =>
|
UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')),
|
||||||
import(
|
UserReportingModal,
|
||||||
'src/components/follow_request_confirm/follow_request_confirm.vue'
|
PostStatusModal,
|
||||||
),
|
EditStatusModal,
|
||||||
),
|
StatusHistoryModal,
|
||||||
SettingsModal: defineAsyncComponent(
|
GlobalNoticeList
|
||||||
() => import('src/components/settings_modal/settings_modal.vue'),
|
|
||||||
),
|
|
||||||
UpdateNotification: defineAsyncComponent(
|
|
||||||
() =>
|
|
||||||
import('src/components/update_notification/update_notification.vue'),
|
|
||||||
),
|
|
||||||
PostStatusModal: defineAsyncComponent(
|
|
||||||
() => import('src/components/post_status_modal/post_status_modal.vue'),
|
|
||||||
),
|
|
||||||
UserReportingModal: defineAsyncComponent(
|
|
||||||
() =>
|
|
||||||
import('src/components/user_reporting_modal/user_reporting_modal.vue'),
|
|
||||||
),
|
|
||||||
EditStatusModal: defineAsyncComponent(
|
|
||||||
() => import('src/components/edit_status_modal/edit_status_modal.vue'),
|
|
||||||
),
|
|
||||||
StatusHistoryModal: defineAsyncComponent(
|
|
||||||
() =>
|
|
||||||
import('src/components/status_history_modal/status_history_modal.vue'),
|
|
||||||
),
|
|
||||||
GlobalError,
|
|
||||||
GlobalNoticeList,
|
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
mobileActivePanel: 'timeline',
|
mobileActivePanel: 'timeline'
|
||||||
updateMobileState: null,
|
|
||||||
updateScrollState: null,
|
|
||||||
}),
|
}),
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
allowNonSquareEmoji: useMergedConfigStore().mergedConfig.nonSquareEmoji,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
watch: {
|
||||||
themeApplied() {
|
themeApplied () {
|
||||||
this.removeSplash()
|
this.removeSplash()
|
||||||
},
|
},
|
||||||
currentTheme() {
|
currentTheme () {
|
||||||
this.setThemeBodyClass()
|
this.setThemeBodyClass()
|
||||||
},
|
},
|
||||||
layoutType() {
|
layoutType () {
|
||||||
document.getElementById('modal').classList = ['-' + this.layoutType]
|
document.getElementById('modal').classList = ['-' + this.layoutType]
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created () {
|
||||||
// Load the locale from the storage
|
// Load the locale from the storage
|
||||||
const value = useMergedConfigStore().mergedConfig.interfaceLanguage
|
const val = this.$store.getters.mergedConfig.interfaceLanguage
|
||||||
useI18nStore().setLanguage(value)
|
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
||||||
useEmojiStore().loadUnicodeEmojiData(value)
|
|
||||||
|
|
||||||
document.getElementById('modal').classList = ['-' + this.layoutType]
|
document.getElementById('modal').classList = ['-' + this.layoutType]
|
||||||
|
|
||||||
// Create bound handlers
|
// Create bound handlers
|
||||||
this.updateScrollState = throttle(this.scrollHandler, 200)
|
this.updateScrollState = throttle(this.scrollHandler, 200)
|
||||||
this.updateMobileState = throttle(this.resizeHandler, 200)
|
this.updateMobileState = throttle(this.resizeHandler, 200)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted () {
|
||||||
window.addEventListener('resize', this.updateMobileState)
|
window.addEventListener('resize', this.updateMobileState)
|
||||||
this.scrollParent.addEventListener('scroll', this.updateScrollState)
|
this.scrollParent.addEventListener('scroll', this.updateScrollState)
|
||||||
|
|
||||||
if (this.themeApplied) {
|
if (useInterfaceStore().themeApplied) {
|
||||||
this.setThemeBodyClass()
|
this.setThemeBodyClass()
|
||||||
this.removeSplash()
|
this.removeSplash()
|
||||||
}
|
}
|
||||||
getOrCreateServiceWorker()
|
getOrCreateServiceWorker()
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted () {
|
||||||
window.removeEventListener('resize', this.updateMobileState)
|
window.removeEventListener('resize', this.updateMobileState)
|
||||||
this.scrollParent.removeEventListener('scroll', this.updateScrollState)
|
this.scrollParent.removeEventListener('scroll', this.updateScrollState)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentTheme() {
|
themeApplied () {
|
||||||
if (this.styleDataUsed) {
|
return useInterfaceStore().themeApplied
|
||||||
const styleMeta = this.styleDataUsed.find(
|
},
|
||||||
(x) => x.component === '@meta',
|
currentTheme () {
|
||||||
)
|
if (useInterfaceStore().styleDataUsed) {
|
||||||
|
const styleMeta = useInterfaceStore().styleDataUsed.find(x => x.component === '@meta')
|
||||||
|
|
||||||
if (styleMeta !== undefined) {
|
if (styleMeta !== undefined) {
|
||||||
return styleMeta.directives.name.replaceAll(' ', '-').toLowerCase()
|
return styleMeta.directives.name.replaceAll(" ", "-").toLowerCase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'stock'
|
return 'stock'
|
||||||
},
|
},
|
||||||
layoutModalClass() {
|
layoutModalClass () {
|
||||||
return '-' + this.layoutType
|
return '-' + this.layoutType
|
||||||
},
|
},
|
||||||
classes() {
|
classes () {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'-reverse': this.reverseLayout,
|
'-reverse': this.reverseLayout,
|
||||||
'-no-sticky-headers': this.noSticky,
|
'-no-sticky-headers': this.noSticky,
|
||||||
'-has-new-post-button': this.newPostButtonShown,
|
'-has-new-post-button': this.newPostButtonShown
|
||||||
},
|
},
|
||||||
'-' + this.layoutType,
|
'-' + this.layoutType
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
navClasses() {
|
navClasses () {
|
||||||
const { navbarColumnStretch } = useMergedConfigStore().mergedConfig
|
const { navbarColumnStretch } = this.$store.getters.mergedConfig
|
||||||
return [
|
return [
|
||||||
'-' + this.layoutType,
|
'-' + this.layoutType,
|
||||||
...(navbarColumnStretch ? ['-column-stretch'] : []),
|
...(navbarColumnStretch ? ['-column-stretch'] : [])
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
userBackground() {
|
currentUser () { return this.$store.state.users.currentUser },
|
||||||
return this.currentUser?.background_image
|
userBackground () { return this.currentUser.background_image },
|
||||||
},
|
instanceBackground () {
|
||||||
foreignProfileBackground() {
|
return this.mergedConfig.hideInstanceWallpaper
|
||||||
return (
|
|
||||||
useMergedConfigStore().mergedConfig.allowForeignUserBackground &&
|
|
||||||
useInterfaceStore().foreignProfileBackground
|
|
||||||
)
|
|
||||||
},
|
|
||||||
instanceBackground() {
|
|
||||||
return useMergedConfigStore().mergedConfig.hideInstanceWallpaper
|
|
||||||
? null
|
? null
|
||||||
: this.instanceBackgroundUrl
|
: this.$store.state.instance.background
|
||||||
},
|
},
|
||||||
background() {
|
background () { return this.userBackground || this.instanceBackground },
|
||||||
return (
|
bgStyle () {
|
||||||
this.foreignProfileBackground ||
|
|
||||||
this.userBackground ||
|
|
||||||
this.instanceBackground
|
|
||||||
)
|
|
||||||
},
|
|
||||||
bgStyle() {
|
|
||||||
if (this.background) {
|
if (this.background) {
|
||||||
return {
|
return {
|
||||||
'--body-background-image': `url(${this.background})`,
|
'--body-background-image': `url(${this.background})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shoutJoined() {
|
shout () { return useShoutStore().joined },
|
||||||
return useShoutStore().joined
|
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
|
||||||
|
showInstanceSpecificPanel () {
|
||||||
|
return this.$store.state.instance.showInstanceSpecificPanel &&
|
||||||
|
!this.$store.getters.mergedConfig.hideISP &&
|
||||||
|
this.$store.state.instance.instanceSpecificPanelContent
|
||||||
},
|
},
|
||||||
isChats() {
|
isChats () {
|
||||||
return (
|
return this.$route.name === 'chat' || this.$route.name === 'chats'
|
||||||
this.$route.name === 'chat' ||
|
|
||||||
this.$route.name === 'chats' ||
|
|
||||||
this.$route.name === 'conversation2'
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
isListEdit() {
|
isListEdit () {
|
||||||
return this.$route.name === 'lists-edit'
|
return this.$route.name === 'lists-edit'
|
||||||
},
|
},
|
||||||
newPostButtonShown() {
|
newPostButtonShown () {
|
||||||
if (this.isChats) return false
|
if (this.isChats) return false
|
||||||
if (this.isListEdit) return false
|
if (this.isListEdit) return false
|
||||||
return (
|
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
|
||||||
useMergedConfigStore().mergedConfig.alwaysShowNewPostButton ||
|
|
||||||
this.layoutType === 'mobile'
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
shoutboxPosition() {
|
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
||||||
return (
|
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
||||||
useMergedConfigStore().mergedConfig.alwaysShowNewPostButton || false
|
shoutboxPosition () {
|
||||||
)
|
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
|
||||||
},
|
},
|
||||||
hideShoutbox() {
|
hideShoutbox () {
|
||||||
return this.isChats || useMergedConfigStore().mergedConfig.hideShoutbox
|
return this.$store.getters.mergedConfig.hideShoutbox
|
||||||
},
|
},
|
||||||
thirdColumnMode() {
|
layoutType () { return useInterfaceStore().layoutType },
|
||||||
return this.mergedConfig.thirdColumnMode
|
privateMode () { return this.$store.state.instance.private },
|
||||||
},
|
reverseLayout () {
|
||||||
reverseSetting() {
|
const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
|
||||||
return this.mergedConfig.sidebarRight
|
|
||||||
},
|
|
||||||
reverseLayout() {
|
|
||||||
if (this.layoutType !== 'wide') {
|
if (this.layoutType !== 'wide') {
|
||||||
return this.reverseSetting
|
return reverseSetting
|
||||||
} else {
|
} else {
|
||||||
return this.thirdColumnMode === 'notifications'
|
return thirdColumnMode === 'notifications' ? reverseSetting : !reverseSetting
|
||||||
? this.reverseSetting
|
|
||||||
: !this.reverseSetting
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
noSticky() {
|
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
|
||||||
return this.mergedConfig.disableStickyHeaders
|
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
|
||||||
},
|
scrollParent () { return window; /* this.$refs.appContentRef */ },
|
||||||
showScrollbars() {
|
...mapGetters(['mergedConfig'])
|
||||||
return this.mergedConfig.showScrollbars
|
|
||||||
},
|
|
||||||
scrollParent() {
|
|
||||||
return window /* this.$refs.appContentRef */
|
|
||||||
},
|
|
||||||
showInstanceSpecificPanel() {
|
|
||||||
return this.instanceSpecificPanelPresent && !this.mergedConfig.hideISP
|
|
||||||
},
|
|
||||||
...mapState(useMergedConfigStore, ['mergedConfig']),
|
|
||||||
...mapState(useInterfaceStore, [
|
|
||||||
'themeApplied',
|
|
||||||
'styleDataUsed',
|
|
||||||
'layoutType',
|
|
||||||
]),
|
|
||||||
...mapState(useUsersStore, ['currentUser']),
|
|
||||||
...mapState(useInstanceStore, ['styleDataUsed']),
|
|
||||||
...mapState(useInstanceCapabilitiesStore, [
|
|
||||||
'suggestionsEnabled',
|
|
||||||
'editingAvailable',
|
|
||||||
]),
|
|
||||||
...mapState(useInstanceStore, {
|
|
||||||
instanceBackgroundUrl: (store) => store.instanceIdentity.background,
|
|
||||||
showFeaturesPanel: (store) => store.instanceIdentity.showFeaturesPanel,
|
|
||||||
instanceSpecificPanelPresent: (store) =>
|
|
||||||
store.instanceIdentity.showInstanceSpecificPanel &&
|
|
||||||
store.instanceIdentity.instanceSpecificPanelContent,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resizeHandler() {
|
resizeHandler () {
|
||||||
useInterfaceStore().setLayoutWidth(windowWidth())
|
useInterfaceStore().setLayoutWidth(windowWidth())
|
||||||
useInterfaceStore().setLayoutHeight(windowHeight())
|
useInterfaceStore().setLayoutHeight(windowHeight())
|
||||||
},
|
},
|
||||||
scrollHandler() {
|
scrollHandler () {
|
||||||
const scrollPosition =
|
const scrollPosition = this.scrollParent === window ? window.scrollY : this.scrollParent.scrollTop
|
||||||
this.scrollParent === window
|
|
||||||
? window.scrollY
|
|
||||||
: this.scrollParent.scrollTop
|
|
||||||
|
|
||||||
if (scrollPosition != 0) {
|
if (scrollPosition != 0) {
|
||||||
this.$refs.appContentRef.classList.add(['-scrolled'])
|
this.$refs.appContentRef.classList.add(['-scrolled'])
|
||||||
|
|
@ -281,10 +189,10 @@ export default {
|
||||||
this.$refs.appContentRef.classList.remove(['-scrolled'])
|
this.$refs.appContentRef.classList.remove(['-scrolled'])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setThemeBodyClass() {
|
setThemeBodyClass () {
|
||||||
const themeName = this.currentTheme
|
const themeName = this.currentTheme
|
||||||
const classList = Array.from(document.body.classList)
|
const classList = Array.from(document.body.classList)
|
||||||
const oldTheme = classList.filter((c) => c.startsWith('theme-'))
|
const oldTheme = classList.filter(c => c.startsWith('theme-'))
|
||||||
|
|
||||||
if (themeName !== null && themeName !== '') {
|
if (themeName !== null && themeName !== '') {
|
||||||
const newTheme = `theme-${themeName.toLowerCase()}`
|
const newTheme = `theme-${themeName.toLowerCase()}`
|
||||||
|
|
@ -300,19 +208,14 @@ export default {
|
||||||
document.body.classList.remove(...oldTheme)
|
document.body.classList.remove(...oldTheme)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeSplash() {
|
removeSplash () {
|
||||||
document.querySelector('#status').textContent = this.$t(
|
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
|
||||||
'splash.fun_' + Math.ceil(Math.random() * 4),
|
|
||||||
)
|
|
||||||
const splashscreenRoot = document.querySelector('#splash')
|
const splashscreenRoot = document.querySelector('#splash')
|
||||||
splashscreenRoot.addEventListener('transitionend', () => {
|
splashscreenRoot.addEventListener('transitionend', () => {
|
||||||
splashscreenRoot.remove()
|
splashscreenRoot.remove()
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
|
||||||
splashscreenRoot.remove() // forcibly remove it, should fix my plasma browser widget t. HJ
|
|
||||||
}, 600)
|
|
||||||
splashscreenRoot.classList.add('hidden')
|
splashscreenRoot.classList.add('hidden')
|
||||||
document.querySelector('#app').classList.remove('hidden')
|
document.querySelector('#app').classList.remove('hidden')
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
189
src/App.scss
189
src/App.scss
|
|
@ -1,10 +1,9 @@
|
||||||
|
// stylelint-disable rscss/class-format
|
||||||
|
/* stylelint-disable no-descending-specificity */
|
||||||
@use "panel";
|
@use "panel";
|
||||||
|
|
||||||
@import '@fortawesome/fontawesome-svg-core/styles.css';
|
@import '@fortawesome/fontawesome-svg-core/styles.css';
|
||||||
@import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css';
|
@import '@floatingghost/pinch-zoom-element/dist/pinch-zoom.css';
|
||||||
|
|
||||||
// stylelint-disable rscss/class-format
|
|
||||||
/* stylelint-disable no-descending-specificity */
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--status-margin: 0.75em;
|
--status-margin: 0.75em;
|
||||||
|
|
@ -22,7 +21,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: var(--textSize, 1rem);
|
font-size: var(--textSize, 14px);
|
||||||
|
|
||||||
--navbar-height: var(--navbarSize, 3.5rem);
|
--navbar-height: var(--navbarSize, 3.5rem);
|
||||||
--emoji-size: var(--emojiSize, 32px);
|
--emoji-size: var(--emojiSize, 32px);
|
||||||
|
|
@ -51,7 +50,7 @@ body {
|
||||||
// have a cursor/pointer to operate them
|
// have a cursor/pointer to operate them
|
||||||
@media (any-pointer: fine) {
|
@media (any-pointer: fine) {
|
||||||
* {
|
* {
|
||||||
scrollbar-color: var(--icon) transparent;
|
scrollbar-color: var(--fg) transparent;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
@ -131,7 +130,7 @@ body {
|
||||||
}
|
}
|
||||||
// Body should have background to scrollbar otherwise it will use white (body color?)
|
// Body should have background to scrollbar otherwise it will use white (body color?)
|
||||||
html {
|
html {
|
||||||
scrollbar-color: var(--icon) var(--wallpaper);
|
scrollbar-color: var(--fg) var(--wallpaper);
|
||||||
background: var(--wallpaper);
|
background: var(--wallpaper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,25 +144,6 @@ h4 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
|
||||||
background: var(--background);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--roundness);
|
|
||||||
padding: 0 0.2em;
|
|
||||||
|
|
||||||
& pre,
|
|
||||||
pre & {
|
|
||||||
display: block;
|
|
||||||
overflow-x: auto;
|
|
||||||
padding: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.pre {
|
|
||||||
white-space: pre;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconLetter {
|
.iconLetter {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -220,7 +200,6 @@ nav {
|
||||||
background-color: var(--wallpaper);
|
background-color: var(--wallpaper);
|
||||||
background-image: var(--body-background-image);
|
background-image: var(--body-background-image);
|
||||||
background-position: 50%;
|
background-position: 50%;
|
||||||
transition: background-image 1s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.underlay {
|
.underlay {
|
||||||
|
|
@ -403,10 +382,6 @@ nav {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-family: var(--font);
|
font-family: var(--font);
|
||||||
|
|
||||||
&.-transparent {
|
|
||||||
backdrop-filter: blur(0.125em) contrast(60%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-focus-inner {
|
&::-moz-focus-inner {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
@ -431,14 +406,6 @@ nav {
|
||||||
button:not(.button-default) {
|
button:not(.button-default) {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
text-align: initial;
|
|
||||||
padding: 0;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
display: inline;
|
|
||||||
font-family: inherit;
|
|
||||||
line-height: unset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
|
@ -446,6 +413,45 @@ nav {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
border-color: var(--border);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
border-top-width: 1px;
|
||||||
|
|
||||||
|
&.-active,
|
||||||
|
&:hover {
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-active + &,
|
||||||
|
&:hover + & {
|
||||||
|
border-top-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover + .menu-item-collapsible:not(.-expanded) + &,
|
||||||
|
&.-active + .menu-item-collapsible:not(.-expanded) + & {
|
||||||
|
border-top-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[aria-expanded="true"] {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-right-radius: var(--roundness);
|
||||||
|
border-top-left-radius: var(--roundness);
|
||||||
|
border-top-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom-right-radius: var(--roundness);
|
||||||
|
border-bottom-left-radius: var(--roundness);
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.menu-item,
|
.menu-item,
|
||||||
.list-item {
|
.list-item {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
@ -464,6 +470,22 @@ nav {
|
||||||
--__line-height: 1.5em;
|
--__line-height: 1.5em;
|
||||||
--__horizontal-gap: 0.75em;
|
--__horizontal-gap: 0.75em;
|
||||||
--__vertical-gap: 0.5em;
|
--__vertical-gap: 0.5em;
|
||||||
|
|
||||||
|
&.-non-interactive {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
button:not(.button-default) {
|
||||||
|
text-align: initial;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
display: inline;
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-unstyled {
|
.button-unstyled {
|
||||||
|
|
@ -487,12 +509,6 @@ nav {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
|
||||||
&.-disabled {
|
|
||||||
color: var(--textFaint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
input,
|
||||||
textarea {
|
textarea {
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -509,10 +525,6 @@ textarea {
|
||||||
height: unset;
|
height: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: var(--textFaint)
|
|
||||||
}
|
|
||||||
|
|
||||||
--_padding: 0.5em;
|
--_padding: 0.5em;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -533,10 +545,6 @@ textarea {
|
||||||
&[disabled="disabled"],
|
&[disabled="disabled"],
|
||||||
&.disabled {
|
&.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
color: var(--textFaint);
|
|
||||||
|
|
||||||
/* stylelint-disable-next-line declaration-no-important */
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&[type="range"] {
|
&[type="range"] {
|
||||||
|
|
@ -562,8 +570,6 @@ textarea {
|
||||||
& + label::before {
|
& + label::before {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
background-color: var(--background);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ label::before {
|
+ label::before {
|
||||||
|
|
@ -663,8 +669,7 @@ option {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: row dense;
|
grid-auto-flow: row dense;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
li {
|
li {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|
@ -674,6 +679,11 @@ option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-block {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-group {
|
.btn-group {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
@ -685,6 +695,7 @@ option {
|
||||||
--_roundness-right: 0;
|
--_roundness-right: 0;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
> *:first-child,
|
> *:first-child,
|
||||||
|
|
@ -731,15 +742,17 @@ option {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-dot {
|
&.-dot {
|
||||||
min-height: 0.6em;
|
min-height: 8px;
|
||||||
max-height: 0.6em;
|
max-height: 8px;
|
||||||
min-width: 0.6em;
|
min-width: 8px;
|
||||||
max-width: 0.6em;
|
max-width: 8px;
|
||||||
left: calc(50% + 0.5em);
|
|
||||||
top: calc(50% - 1em);
|
|
||||||
line-height: 0;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
line-height: 0;
|
||||||
|
font-size: 0;
|
||||||
|
left: calc(50% - 4px);
|
||||||
|
top: calc(50% - 4px);
|
||||||
|
margin-left: 6px;
|
||||||
|
margin-top: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-counter {
|
&.-counter {
|
||||||
|
|
@ -761,19 +774,6 @@ option {
|
||||||
padding: 0 0.25em;
|
padding: 0 0.25em;
|
||||||
border-radius: var(--roundness);
|
border-radius: var(--roundness);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|
||||||
&.-dismissible {
|
|
||||||
display: flex;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
margin: 0;
|
|
||||||
align-items: baseline;
|
|
||||||
line-height: 2;
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: block;
|
|
||||||
flex: 1 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.faint {
|
.faint {
|
||||||
|
|
@ -783,20 +783,21 @@ option {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-dismissible {
|
.visibility-notice {
|
||||||
display: flex;
|
padding: 0.5em;
|
||||||
padding: 0.75em 1em;
|
border: 1px solid var(--textFaint);
|
||||||
align-items: baseline;
|
border-radius: var(--roundness);
|
||||||
line-height: 1.5;
|
}
|
||||||
|
|
||||||
p,
|
.notice-dismissible {
|
||||||
span {
|
padding-right: 4rem;
|
||||||
display: block;
|
position: relative;
|
||||||
flex: 1 1 auto;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dismiss {
|
.dismiss {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0.5em;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -923,9 +924,7 @@ option {
|
||||||
margin: -1px;
|
margin: -1px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
/* stylelint-disable property-no-deprecated */
|
|
||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
/* stylelint-enable property-no-deprecated */
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
@ -937,6 +936,12 @@ option {
|
||||||
|
|
||||||
#splash {
|
#splash {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#status {
|
#status {
|
||||||
&.css-ok {
|
&.css-ok {
|
||||||
|
|
@ -1075,7 +1080,7 @@ option {
|
||||||
scale: 1.0063 0.9938;
|
scale: 1.0063 0.9938;
|
||||||
translate: 0 -10%;
|
translate: 0 -10%;
|
||||||
transform: rotateZ(var(--defaultZ));
|
transform: rotateZ(var(--defaultZ));
|
||||||
animation-timing-function: ease-in-out;
|
animation-timing-function: ease-in-ou;
|
||||||
}
|
}
|
||||||
|
|
||||||
90% {
|
90% {
|
||||||
|
|
|
||||||
16
src/App.vue
16
src/App.vue
|
|
@ -26,12 +26,12 @@
|
||||||
class="column -scrollable"
|
class="column -scrollable"
|
||||||
:class="{ '-show-scrollbar': showScrollbars }"
|
:class="{ '-show-scrollbar': showScrollbars }"
|
||||||
>
|
>
|
||||||
<UserPanel />
|
<user-panel />
|
||||||
<template v-if="layoutType !== 'mobile'">
|
<template v-if="layoutType !== 'mobile'">
|
||||||
<NavPanel />
|
<nav-panel />
|
||||||
<InstanceSpecificPanel v-if="showInstanceSpecificPanel" />
|
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
||||||
<FeaturesPanel v-if="!currentUser && showFeaturesPanel" />
|
<features-panel v-if="!currentUser && showFeaturesPanel" />
|
||||||
<WhoToFollowPanel v-if="currentUser && suggestionsEnabled" />
|
<who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
|
||||||
<div id="notifs-sidebar" />
|
<div id="notifs-sidebar" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -60,8 +60,8 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MediaModal />
|
<MediaModal />
|
||||||
<ShoutPanel
|
<shout-panel
|
||||||
v-if="currentUser && !hideShoutbox && shoutJoined"
|
v-if="currentUser && shout && !hideShoutbox"
|
||||||
:floating="true"
|
:floating="true"
|
||||||
class="floating-shout mobile-hidden"
|
class="floating-shout mobile-hidden"
|
||||||
:class="{ '-left': shoutboxPosition }"
|
:class="{ '-left': shoutboxPosition }"
|
||||||
|
|
@ -73,9 +73,7 @@
|
||||||
<StatusHistoryModal v-if="editingAvailable" />
|
<StatusHistoryModal v-if="editingAvailable" />
|
||||||
<SettingsModal :class="layoutModalClass" />
|
<SettingsModal :class="layoutModalClass" />
|
||||||
<UpdateNotification />
|
<UpdateNotification />
|
||||||
<GlobalError />
|
|
||||||
<GlobalNoticeList />
|
<GlobalNoticeList />
|
||||||
<FollowRequestConfirm v-if="currentUser" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
491
src/api/admin.js
491
src/api/admin.js
|
|
@ -1,491 +0,0 @@
|
||||||
import { promisedRequest } from './helpers.js'
|
|
||||||
|
|
||||||
const REPORTS = '/api/v1/pleroma/admin/reports'
|
|
||||||
const CONFIG_URL = '/api/v1/pleroma/admin/config'
|
|
||||||
const DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions'
|
|
||||||
|
|
||||||
const ANNOUNCEMENTS_URL = (id = '') =>
|
|
||||||
`/api/v1/pleroma/admin/announcements/${id}`
|
|
||||||
|
|
||||||
const FRONTENDS_URL = '/api/v1/pleroma/admin/frontends'
|
|
||||||
const FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install'
|
|
||||||
|
|
||||||
const USERS_URL = (nickname = '') => `/api/v1/pleroma/admin/users/${nickname}`
|
|
||||||
const USERS_URL_LIST = ({
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
filters = {},
|
|
||||||
query = '',
|
|
||||||
name = '',
|
|
||||||
email = '',
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
local = false,
|
|
||||||
external = false,
|
|
||||||
active = false,
|
|
||||||
needApproval = false,
|
|
||||||
unconfirmed = false,
|
|
||||||
deactivated = false,
|
|
||||||
isAdmin = true,
|
|
||||||
isModerator = true,
|
|
||||||
} = filters
|
|
||||||
const filters_str = [
|
|
||||||
local && 'local',
|
|
||||||
external && 'external',
|
|
||||||
active && 'active',
|
|
||||||
needApproval && 'need_approval',
|
|
||||||
unconfirmed && 'unconfirmed',
|
|
||||||
deactivated && 'deactivated',
|
|
||||||
isAdmin && 'is_admin',
|
|
||||||
isModerator && 'is_moderator',
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(',')
|
|
||||||
return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
|
|
||||||
|
|
||||||
const PERMISSION_GROUP_URL = (right) =>
|
|
||||||
`/api/pleroma/admin/users/permission_group/${right}`
|
|
||||||
const ACTIVATE_USERS_URL = '/api/pleroma/admin/users/activate'
|
|
||||||
const DEACTIVATE_USERS_URL = '/api/pleroma/admin/users/deactivate'
|
|
||||||
const SUGGEST_USERS_URL = '/api/pleroma/admin/users/suggest'
|
|
||||||
const UNSUGGEST_USERS_URL = '/api/pleroma/admin/users/unsuggest'
|
|
||||||
const APPROVE_USERS_URL = '/api/v1/pleroma/admin/users/approve'
|
|
||||||
const CONFIRM_USERS_URL = '/api/v1/pleroma/admin/users/confirm_email'
|
|
||||||
const RESEND_CONFIRMATION_EMAIL_URL =
|
|
||||||
'/api/v1/pleroma/admin/users/resend_confirmation_email'
|
|
||||||
const LIST_STATUSES_URL = ({ id, page, pageSize, godmode, withReblogs }) =>
|
|
||||||
`/api/v1/pleroma/admin/users/${id}/statuses?page_size=${pageSize}&page=${page}&godmode=${godmode}&with_reblogs=${withReblogs}`
|
|
||||||
const CHANGE_STATUS_SCOPE_URL = (id) => `/api/v1/pleroma/admin/statuses/${id}`
|
|
||||||
const REQUIRE_PASSWORD_CHANGE_URL =
|
|
||||||
'/api/v1/pleroma/admin/users/force_password_reset'
|
|
||||||
|
|
||||||
const DISABLE_MFA_URL = '/api/v1/pleroma/admin/users/disable_mfa'
|
|
||||||
const EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
|
|
||||||
const EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
|
|
||||||
const EMOJI_PACK_URL = (name) => `/api/v1/pleroma/emoji/pack?name=${name}`
|
|
||||||
const EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download'
|
|
||||||
const EMOJI_PACKS_DL_REMOTE_ZIP_URL = '/api/v1/pleroma/emoji/packs/download_zip'
|
|
||||||
const EMOJI_PACKS_LS_REMOTE_URL = (url, page, pageSize) =>
|
|
||||||
`/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}`
|
|
||||||
const EMOJI_UPDATE_FILE_URL = (name) =>
|
|
||||||
`/api/v1/pleroma/emoji/packs/files?name=${name}`
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
export const setUsersTags = ({
|
|
||||||
tags,
|
|
||||||
credentials,
|
|
||||||
value,
|
|
||||||
screen_names: nicknames,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: TAG_USER_URL,
|
|
||||||
method: value ? 'PUT' : 'DELETE',
|
|
||||||
credentials,
|
|
||||||
payload: {
|
|
||||||
nicknames,
|
|
||||||
tags,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const setUsersRight = ({
|
|
||||||
right,
|
|
||||||
credentials,
|
|
||||||
value,
|
|
||||||
screen_names: nicknames,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: PERMISSION_GROUP_URL(right),
|
|
||||||
method: value ? 'POST' : 'DELETE',
|
|
||||||
credentials,
|
|
||||||
payload: {
|
|
||||||
nicknames,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const setUsersActivationStatus = ({
|
|
||||||
credentials,
|
|
||||||
screen_names: nicknames,
|
|
||||||
value,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: value ? ACTIVATE_USERS_URL : DEACTIVATE_USERS_URL,
|
|
||||||
method: 'PATCH',
|
|
||||||
credentials,
|
|
||||||
payload: {
|
|
||||||
nicknames,
|
|
||||||
},
|
|
||||||
}).then((response) => response.users)
|
|
||||||
|
|
||||||
export const setUsersApprovalStatus = ({
|
|
||||||
credentials,
|
|
||||||
screen_names: nicknames,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: APPROVE_USERS_URL,
|
|
||||||
method: 'PATCH',
|
|
||||||
credentials,
|
|
||||||
payload: {
|
|
||||||
nicknames,
|
|
||||||
},
|
|
||||||
}).then((response) => response.users)
|
|
||||||
|
|
||||||
export const setUsersConfirmationStatus = ({
|
|
||||||
credentials,
|
|
||||||
screen_names: nicknames,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: CONFIRM_USERS_URL,
|
|
||||||
method: 'PATCH',
|
|
||||||
credentials,
|
|
||||||
payload: {
|
|
||||||
nicknames,
|
|
||||||
},
|
|
||||||
}).then((response) => response.users)
|
|
||||||
|
|
||||||
export const setUsersSuggestionStatus = ({
|
|
||||||
credentials,
|
|
||||||
screen_names: nicknames,
|
|
||||||
value,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: value ? SUGGEST_USERS_URL : UNSUGGEST_USERS_URL,
|
|
||||||
method: 'PATCH',
|
|
||||||
credentials,
|
|
||||||
payload: {
|
|
||||||
nicknames,
|
|
||||||
},
|
|
||||||
}).then((response) => response.users)
|
|
||||||
|
|
||||||
export const getUserData = ({ credentials, screen_name: nickname }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: USERS_URL(nickname),
|
|
||||||
method: 'GET',
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deleteAccounts = ({ credentials, screen_names: nicknames }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: USERS_URL(),
|
|
||||||
method: 'DELETE',
|
|
||||||
credentials,
|
|
||||||
payload: {
|
|
||||||
nicknames,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getAnnouncements = ({ id, credentials }) =>
|
|
||||||
promisedRequest({ url: ANNOUNCEMENTS_URL(id), credentials })
|
|
||||||
|
|
||||||
// the reported list is hardly useful because standards are for dating i guess,
|
|
||||||
// so make sure to fetchIfMissing right afterward using this call
|
|
||||||
export const listUsers = ({ opts, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: USERS_URL_LIST(opts),
|
|
||||||
credentials,
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const resendConfirmationEmail = ({
|
|
||||||
screen_names: nicknames,
|
|
||||||
credentials,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: RESEND_CONFIRMATION_EMAIL_URL,
|
|
||||||
credentials,
|
|
||||||
method: 'PATCH',
|
|
||||||
payload: {
|
|
||||||
nicknames,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const requirePasswordChange = ({
|
|
||||||
screen_names: nicknames,
|
|
||||||
credentials,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: REQUIRE_PASSWORD_CHANGE_URL,
|
|
||||||
credentials,
|
|
||||||
method: 'PATCH',
|
|
||||||
payload: {
|
|
||||||
nicknames,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const disableMFA = ({ screen_name: nickname, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: DISABLE_MFA_URL,
|
|
||||||
credentials,
|
|
||||||
method: 'PUT',
|
|
||||||
payload: {
|
|
||||||
nickname,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const listStatuses = ({ opts, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: LIST_STATUSES_URL(opts),
|
|
||||||
credentials,
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const changeStatusScope = ({
|
|
||||||
opts: { id, sensitive, visibility },
|
|
||||||
credentials,
|
|
||||||
}) => {
|
|
||||||
const payload = {}
|
|
||||||
if (sensitive !== undefined) {
|
|
||||||
payload['sensitive'] = sensitive
|
|
||||||
}
|
|
||||||
if (visibility !== undefined) {
|
|
||||||
payload['visibility'] = visibility
|
|
||||||
}
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: CHANGE_STATUS_SCOPE_URL(id),
|
|
||||||
credentials,
|
|
||||||
method: 'PUT',
|
|
||||||
payload,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const announcementToPayload = ({
|
|
||||||
content,
|
|
||||||
startsAt,
|
|
||||||
endsAt,
|
|
||||||
allDay,
|
|
||||||
}) => {
|
|
||||||
const payload = { content }
|
|
||||||
|
|
||||||
if (startsAt !== undefined) {
|
|
||||||
payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endsAt !== undefined) {
|
|
||||||
payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allDay !== undefined) {
|
|
||||||
payload.all_day = allDay
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload
|
|
||||||
}
|
|
||||||
|
|
||||||
export const postAnnouncement = ({
|
|
||||||
credentials,
|
|
||||||
content,
|
|
||||||
startsAt,
|
|
||||||
endsAt,
|
|
||||||
allDay,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: ANNOUNCEMENTS_URL(),
|
|
||||||
credentials,
|
|
||||||
method: 'POST',
|
|
||||||
payload: announcementToPayload({ content, startsAt, endsAt, allDay }),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const editAnnouncement = ({
|
|
||||||
id,
|
|
||||||
credentials,
|
|
||||||
content,
|
|
||||||
startsAt,
|
|
||||||
endsAt,
|
|
||||||
allDay,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: ANNOUNCEMENTS_URL(id),
|
|
||||||
credentials,
|
|
||||||
method: 'PATCH',
|
|
||||||
payload: announcementToPayload({ content, startsAt, endsAt, allDay }),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deleteAnnouncement = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: ANNOUNCEMENTS_URL(id),
|
|
||||||
credentials,
|
|
||||||
method: 'DELETE',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const setReportState = ({ id, state, credentials }) => {
|
|
||||||
return promisedRequest({
|
|
||||||
url: REPORTS,
|
|
||||||
credentials,
|
|
||||||
method: 'PATCH',
|
|
||||||
payload: {
|
|
||||||
reports: [
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
state,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getInstanceDBConfig = ({ credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: CONFIG_URL,
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getInstanceConfigDescriptions = ({ credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: DESCRIPTIONS_URL,
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getAvailableFrontends = ({ credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: FRONTENDS_URL,
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const pushInstanceDBConfig = ({ credentials, payload }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: CONFIG_URL,
|
|
||||||
method: 'POST',
|
|
||||||
credentials,
|
|
||||||
payload,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const installFrontend = ({ credentials, payload }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: FRONTENDS_INSTALL_URL,
|
|
||||||
credentials,
|
|
||||||
method: 'POST',
|
|
||||||
payload,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Emoji packs
|
|
||||||
export const deleteEmojiPack = ({ name }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: EMOJI_PACK_URL(name),
|
|
||||||
method: 'DELETE',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const reloadEmoji = ({ credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: EMOJI_RELOAD_URL,
|
|
||||||
method: 'POST',
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const importEmojiFromFS = ({ credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: EMOJI_IMPORT_FS_URL,
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const createEmojiPack = ({ name, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: EMOJI_PACK_URL(name),
|
|
||||||
method: 'POST',
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const listRemoteEmojiPacks = ({
|
|
||||||
instance,
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
credentials,
|
|
||||||
}) => {
|
|
||||||
if (!instance.startsWith('http')) {
|
|
||||||
instance = 'https://' + instance
|
|
||||||
}
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize),
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const downloadRemoteEmojiPack = ({
|
|
||||||
instance,
|
|
||||||
packName,
|
|
||||||
as,
|
|
||||||
credentials,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: EMOJI_PACKS_DL_REMOTE_URL,
|
|
||||||
credentials,
|
|
||||||
method: 'POST',
|
|
||||||
payload: {
|
|
||||||
url: instance,
|
|
||||||
name: packName,
|
|
||||||
as,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const downloadRemoteEmojiPackZIP = ({
|
|
||||||
url,
|
|
||||||
packName,
|
|
||||||
file,
|
|
||||||
credentials,
|
|
||||||
}) => {
|
|
||||||
const data = new FormData()
|
|
||||||
if (file) data.set('file', file)
|
|
||||||
if (url) data.set('url', url)
|
|
||||||
data.set('name', packName)
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: EMOJI_PACKS_DL_REMOTE_ZIP_URL,
|
|
||||||
method: 'POST',
|
|
||||||
formData: data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const saveEmojiPackMetadata = ({ name, newData, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: EMOJI_PACK_URL(name),
|
|
||||||
credentials,
|
|
||||||
method: 'PATCH',
|
|
||||||
payload: { metadata: newData },
|
|
||||||
})
|
|
||||||
|
|
||||||
export const addNewEmojiFile = ({ packName, file, shortcode, filename }) => {
|
|
||||||
const data = new FormData()
|
|
||||||
if (filename.trim() !== '') {
|
|
||||||
data.set('filename', filename)
|
|
||||||
}
|
|
||||||
if (shortcode.trim() !== '') {
|
|
||||||
data.set('shortcode', shortcode)
|
|
||||||
}
|
|
||||||
data.set('file', file)
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: EMOJI_UPDATE_FILE_URL(packName),
|
|
||||||
method: 'POST',
|
|
||||||
formData: data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateEmojiFile = ({
|
|
||||||
packName,
|
|
||||||
shortcode,
|
|
||||||
newShortcode,
|
|
||||||
newFilename,
|
|
||||||
credentials,
|
|
||||||
force,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: EMOJI_UPDATE_FILE_URL(packName),
|
|
||||||
credentials,
|
|
||||||
method: 'PATCH',
|
|
||||||
payload: {
|
|
||||||
shortcode,
|
|
||||||
new_shortcode: newShortcode,
|
|
||||||
new_filename: newFilename,
|
|
||||||
force,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deleteEmojiFile = ({ packName, shortcode }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: `${EMOJI_UPDATE_FILE_URL(packName)}&shortcode=${shortcode}`,
|
|
||||||
method: 'DELETE',
|
|
||||||
})
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
import { paramsString, promisedRequest } from './helpers.js'
|
|
||||||
|
|
||||||
import {
|
|
||||||
parseChat,
|
|
||||||
parseChatMessage,
|
|
||||||
} from 'src/services/entity_normalizer/entity_normalizer.service.js'
|
|
||||||
|
|
||||||
const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats'
|
|
||||||
const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}`
|
|
||||||
const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit } = {}) =>
|
|
||||||
`/api/v1/pleroma/chats/${id}/messages${paramsString({ maxId, sinceId, limit })}`
|
|
||||||
const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read`
|
|
||||||
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) =>
|
|
||||||
`/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
|
|
||||||
|
|
||||||
export const chats = ({ credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: PLEROMA_CHATS_URL,
|
|
||||||
credentials,
|
|
||||||
}).then(({ data }) => ({
|
|
||||||
data: data.map(parseChat).filter(Boolean),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const getOrCreateChat = ({ accountId, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: PLEROMA_CHAT_URL(accountId),
|
|
||||||
method: 'POST',
|
|
||||||
credentials,
|
|
||||||
}).then(({ data }) => ({ data: parseChat(data) }))
|
|
||||||
|
|
||||||
export const chatMessages = ({
|
|
||||||
id,
|
|
||||||
credentials,
|
|
||||||
maxId,
|
|
||||||
sinceId,
|
|
||||||
limit = 20,
|
|
||||||
}) => {
|
|
||||||
return promisedRequest({
|
|
||||||
url: PLEROMA_CHAT_MESSAGES_URL(id, { maxId, sinceId, limit }),
|
|
||||||
method: 'GET',
|
|
||||||
credentials,
|
|
||||||
}).then(({ data }) => ({
|
|
||||||
data: data.map(parseChatMessage).filter(Boolean),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendChatMessage = ({
|
|
||||||
id,
|
|
||||||
content,
|
|
||||||
mediaId = null,
|
|
||||||
idempotencyKey,
|
|
||||||
credentials,
|
|
||||||
}) => {
|
|
||||||
const payload = {
|
|
||||||
content,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaId) {
|
|
||||||
payload.media_id = mediaId
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = {}
|
|
||||||
|
|
||||||
if (idempotencyKey) {
|
|
||||||
headers['idempotency-key'] = idempotencyKey
|
|
||||||
}
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: PLEROMA_CHAT_MESSAGES_URL(id),
|
|
||||||
method: 'POST',
|
|
||||||
payload,
|
|
||||||
credentials,
|
|
||||||
headers,
|
|
||||||
}).then(({ data }) => ({
|
|
||||||
data: parseChatMessage(data),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const readChat = ({ id, lastReadId, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: PLEROMA_CHAT_READ_URL(id),
|
|
||||||
method: 'POST',
|
|
||||||
payload: {
|
|
||||||
last_read_id: lastReadId,
|
|
||||||
},
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deleteChatMessage = ({ chatId, messageId, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId),
|
|
||||||
method: 'DELETE',
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
import { snakeCase } from 'lodash-es'
|
|
||||||
|
|
||||||
import { StatusCodeError } from 'src/services/errors/errors'
|
|
||||||
|
|
||||||
export const paramsString = (params = {}) => {
|
|
||||||
if (params == null || params === undefined) return ''
|
|
||||||
|
|
||||||
if (typeof params !== 'object' || Array.isArray(params)) {
|
|
||||||
throw new TypeError('Params are not an object!')
|
|
||||||
}
|
|
||||||
|
|
||||||
const entries = (() => {
|
|
||||||
if (params instanceof Map) {
|
|
||||||
return [...params.entries()]
|
|
||||||
} else {
|
|
||||||
return Object.entries(params)
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
const arrays = []
|
|
||||||
const nonArrays = []
|
|
||||||
|
|
||||||
entries.forEach(([k, v]) => {
|
|
||||||
if (v == null) return // Drop nulls
|
|
||||||
if (
|
|
||||||
(typeof v === 'object' && !Array.isArray(v)) ||
|
|
||||||
typeof v === 'function'
|
|
||||||
) {
|
|
||||||
throw new TypeError('Param cannot be non-primitive!')
|
|
||||||
}
|
|
||||||
if (Array.isArray(v)) {
|
|
||||||
arrays.push([k, v])
|
|
||||||
} else {
|
|
||||||
nonArrays.push([k, v])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
arrays.forEach(([k, array]) => {
|
|
||||||
array.forEach((v) => {
|
|
||||||
if (typeof v === 'object' || typeof v === 'function' || v === undefined)
|
|
||||||
throw new TypeError('Array param cannot contain non-primitives!')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (nonArrays.length + arrays.length === 0) return ''
|
|
||||||
|
|
||||||
return (
|
|
||||||
'?' +
|
|
||||||
[
|
|
||||||
...nonArrays.map(([k, v]) => [snakeCase(k), v]),
|
|
||||||
// turning [a,[1,2,3]] into [[a[],1],[a[],2],[a[],3]]
|
|
||||||
...arrays.reduce(
|
|
||||||
(acc, [k, arrayValue]) => [
|
|
||||||
...acc,
|
|
||||||
...arrayValue.map((v) => [snakeCase(k) + '[]', v]),
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
.map(([k, v]) => `${k}=${window.encodeURIComponent(v)}`)
|
|
||||||
.join('&')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const promisedRequest = async ({
|
|
||||||
method = 'GET',
|
|
||||||
url,
|
|
||||||
payload,
|
|
||||||
formData,
|
|
||||||
forceContentType,
|
|
||||||
cache,
|
|
||||||
credentials,
|
|
||||||
headers = {},
|
|
||||||
}) => {
|
|
||||||
const options = {
|
|
||||||
method,
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
Accept: forceContentType ?? 'application/json',
|
|
||||||
...headers,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formData) {
|
|
||||||
options.headers['Content-Type'] = 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cache) {
|
|
||||||
options.cache = cache
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formData || payload) {
|
|
||||||
options.body = formData || JSON.stringify(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (credentials) {
|
|
||||||
options.headers = {
|
|
||||||
...options.headers,
|
|
||||||
...authHeaders(credentials),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, options)
|
|
||||||
const data = await (async () => {
|
|
||||||
const [contentType] = response.headers
|
|
||||||
.get('content-type')
|
|
||||||
.split(';')
|
|
||||||
.map((x) => x.toLowerCase().trim())
|
|
||||||
const contentLength = Number.parseInt(
|
|
||||||
response.headers.get('content-length'),
|
|
||||||
)
|
|
||||||
if (contentLength === 0) return null
|
|
||||||
|
|
||||||
switch (forceContentType ?? contentType) {
|
|
||||||
case 'text/plain':
|
|
||||||
return await response.text()
|
|
||||||
case 'application/json':
|
|
||||||
return await response.json()
|
|
||||||
default:
|
|
||||||
return await response.bytes()
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
const { ok, status } = response
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
return { response, status, data, timestamp: Date.now() }
|
|
||||||
} else {
|
|
||||||
throw new StatusCodeError(response.status, data, { url, options }, response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const authHeaders = (accessToken) => {
|
|
||||||
if (accessToken) {
|
|
||||||
return { Authorization: `Bearer ${accessToken}` }
|
|
||||||
} else {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import { promisedRequest } from './helpers.js'
|
|
||||||
|
|
||||||
export const verifyOTPCode = ({
|
|
||||||
clientId,
|
|
||||||
clientSecret,
|
|
||||||
instance,
|
|
||||||
mfaToken,
|
|
||||||
code,
|
|
||||||
}) => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
|
|
||||||
formData.append('client_id', clientId)
|
|
||||||
formData.append('client_secret', clientSecret)
|
|
||||||
formData.append('mfa_token', mfaToken)
|
|
||||||
formData.append('code', code)
|
|
||||||
formData.append('challenge_type', 'totp')
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: '/oauth/mfa/challenge',
|
|
||||||
method: 'POST',
|
|
||||||
formData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const verifyRecoveryCode = ({
|
|
||||||
clientId,
|
|
||||||
clientSecret,
|
|
||||||
instance,
|
|
||||||
mfaToken,
|
|
||||||
code,
|
|
||||||
}) => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
|
|
||||||
formData.append('client_id', clientId)
|
|
||||||
formData.append('client_secret', clientSecret)
|
|
||||||
formData.append('mfa_token', mfaToken)
|
|
||||||
formData.append('code', code)
|
|
||||||
formData.append('challenge_type', 'recovery')
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: `${instance}/oauth/mfa/challenge`,
|
|
||||||
method: 'POST',
|
|
||||||
formData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
146
src/api/oauth.js
146
src/api/oauth.js
|
|
@ -1,146 +0,0 @@
|
||||||
import { paramsString, promisedRequest } from './helpers.js'
|
|
||||||
|
|
||||||
const REDIRECT_URI = `${window.location.origin}/oauth-callback`
|
|
||||||
|
|
||||||
export const MASTODON_APP_VERIFY_URL = '/api/v1/apps/verify_credentials'
|
|
||||||
export const MASTODON_APP_URL = '/api/v1/apps'
|
|
||||||
export const OAUTH_TOKEN_URL = '/oauth/token'
|
|
||||||
export const OAUTH_MFA_CHALLENGE_URL = '/oauth/mfa/challenge'
|
|
||||||
export const OAUTH_REVOKE_URL = '/oauth/revoke'
|
|
||||||
|
|
||||||
export const createApp = () => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
|
|
||||||
formData.append('client_name', 'PleromaFE')
|
|
||||||
formData.append('website', 'https://pleroma.social')
|
|
||||||
formData.append('redirect_uris', REDIRECT_URI)
|
|
||||||
formData.append('scopes', 'read write follow push admin')
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
method: 'POST',
|
|
||||||
url: MASTODON_APP_URL,
|
|
||||||
formData,
|
|
||||||
}).then(({ data, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
data: {
|
|
||||||
...data,
|
|
||||||
clientId: data.client_id,
|
|
||||||
clientSecret: data.client_secret,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const verifyAppToken = ({ credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_APP_VERIFY_URL,
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getLoginUrl = ({ instance, clientId }) => {
|
|
||||||
const data = {
|
|
||||||
responseType: 'code',
|
|
||||||
clientId,
|
|
||||||
redirectUri: REDIRECT_URI,
|
|
||||||
scope: 'read write follow push admin',
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${instance}/oauth/authorize${paramsString(data)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getTokenWithCredentials = ({
|
|
||||||
clientId,
|
|
||||||
clientSecret,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
}) => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
|
|
||||||
formData.append('client_id', clientId)
|
|
||||||
formData.append('client_secret', clientSecret)
|
|
||||||
formData.append('grant_type', 'password')
|
|
||||||
formData.append('username', username)
|
|
||||||
formData.append('password', password)
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: OAUTH_TOKEN_URL,
|
|
||||||
method: 'POST',
|
|
||||||
formData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getToken = ({ clientId, clientSecret, code }) => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
|
|
||||||
formData.append('client_id', clientId)
|
|
||||||
formData.append('client_secret', clientSecret)
|
|
||||||
formData.append('grant_type', 'authorization_code')
|
|
||||||
formData.append('code', code)
|
|
||||||
formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: OAUTH_TOKEN_URL,
|
|
||||||
method: 'POST',
|
|
||||||
formData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getClientToken = ({ clientId, clientSecret }) => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
|
|
||||||
formData.append('client_id', clientId)
|
|
||||||
formData.append('client_secret', clientSecret)
|
|
||||||
formData.append('grant_type', 'client_credentials')
|
|
||||||
formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: OAUTH_TOKEN_URL,
|
|
||||||
method: 'POST',
|
|
||||||
formData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const verifyOTPCode = ({ app, mfaToken, code }) => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
|
|
||||||
formData.append('client_id', app.client_id)
|
|
||||||
formData.append('client_secret', app.client_secret)
|
|
||||||
formData.append('mfa_token', mfaToken)
|
|
||||||
formData.append('code', code)
|
|
||||||
formData.append('challenge_type', 'totp')
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: OAUTH_MFA_CHALLENGE_URL,
|
|
||||||
method: 'POST',
|
|
||||||
formData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const verifyRecoveryCode = ({ app, mfaToken, code }) => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
|
|
||||||
formData.append('client_id', app.client_id)
|
|
||||||
formData.append('client_secret', app.client_secret)
|
|
||||||
formData.append('mfa_token', mfaToken)
|
|
||||||
formData.append('code', code)
|
|
||||||
formData.append('challenge_type', 'recovery')
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: OAUTH_MFA_CHALLENGE_URL,
|
|
||||||
method: 'POST',
|
|
||||||
formData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const revokeToken = ({ app, token }) => {
|
|
||||||
const formData = new window.FormData()
|
|
||||||
|
|
||||||
formData.append('client_id', app.clientId)
|
|
||||||
formData.append('client_secret', app.clientSecret)
|
|
||||||
formData.append('token', token)
|
|
||||||
|
|
||||||
return promisedRequest({
|
|
||||||
url: OAUTH_REVOKE_URL,
|
|
||||||
method: 'POST',
|
|
||||||
formData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,299 +0,0 @@
|
||||||
import { paramsString, promisedRequest } from './helpers.js'
|
|
||||||
import { MASTODON_USER_TIMELINE_URL } from './timelines.js'
|
|
||||||
|
|
||||||
import {
|
|
||||||
parseSource,
|
|
||||||
parseStatus,
|
|
||||||
parseUser,
|
|
||||||
} from 'src/services/entity_normalizer/entity_normalizer.service.js'
|
|
||||||
|
|
||||||
const MASTODON_SUGGESTIONS_URL = '/api/v1/suggestions'
|
|
||||||
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
|
|
||||||
const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
|
|
||||||
const MASTODON_PASSWORD_RESET_URL = ({ email }) =>
|
|
||||||
`/auth/password${paramsString({ email })}`
|
|
||||||
|
|
||||||
export const MASTODON_FOLLOWING_URL = (
|
|
||||||
id,
|
|
||||||
{ minId, maxId, sinceId, limit, withRelationships },
|
|
||||||
) =>
|
|
||||||
`/api/v1/accounts/${id}/following${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
|
|
||||||
export const MASTODON_FOLLOWERS_URL = (
|
|
||||||
id,
|
|
||||||
{ minId, maxId, sinceId, limit, withRelationships },
|
|
||||||
) =>
|
|
||||||
`/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
|
|
||||||
|
|
||||||
export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}`
|
|
||||||
export const MASTODON_STATUS_CONTEXT_URL = (id) =>
|
|
||||||
`/api/v1/statuses/${id}/context`
|
|
||||||
export const MASTODON_STATUS_SOURCE_URL = (id) =>
|
|
||||||
`/api/v1/statuses/${id}/source`
|
|
||||||
export const MASTODON_STATUS_HISTORY_URL = (id) =>
|
|
||||||
`/api/v1/statuses/${id}/history`
|
|
||||||
const MASTODON_USER_URL = '/api/v1/accounts'
|
|
||||||
const MASTODON_USER_LOOKUP_URL = ({ acct }) =>
|
|
||||||
`/api/v1/accounts/lookup${paramsString({ acct })}`
|
|
||||||
const MASTODON_POLL_URL = (id = '') => `/api/v1/polls/${id}`
|
|
||||||
export const MASTODON_STATUS_FAVORITEDBY_URL = (id) =>
|
|
||||||
`/api/v1/statuses/${id}/favourited_by`
|
|
||||||
export const MASTODON_STATUS_REBLOGGEDBY_URL = (id) =>
|
|
||||||
`/api/v1/statuses/${id}/reblogged_by`
|
|
||||||
const MASTODON_SEARCH_2 = ({
|
|
||||||
q,
|
|
||||||
resolve,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
following,
|
|
||||||
type,
|
|
||||||
withRelationships,
|
|
||||||
accountId,
|
|
||||||
excludeUnreviewed,
|
|
||||||
}) =>
|
|
||||||
`/api/v2/search${paramsString({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed })}`
|
|
||||||
const MASTODON_USER_SEARCH_URL = ({ q, resolve }) =>
|
|
||||||
`/api/v1/accounts/search${paramsString({ q, resolve })}`
|
|
||||||
const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
|
|
||||||
export const PLEROMA_EMOJI_REACTIONS_URL = (id) =>
|
|
||||||
`/api/v1/pleroma/statuses/${id}/reactions`
|
|
||||||
const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) =>
|
|
||||||
`/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}`
|
|
||||||
|
|
||||||
const EMOJI_PACKS_URL = (page, pageSize) =>
|
|
||||||
`/api/v1/pleroma/emoji/packs${paramsString({ page, pageSize })}`
|
|
||||||
|
|
||||||
// Params needed:
|
|
||||||
// nickname
|
|
||||||
// email
|
|
||||||
// fullname
|
|
||||||
// password
|
|
||||||
// password_confirm
|
|
||||||
//
|
|
||||||
// Optional
|
|
||||||
// bio
|
|
||||||
// homepage
|
|
||||||
// location
|
|
||||||
// token
|
|
||||||
// language
|
|
||||||
export const register = ({ params, credentials }) => {
|
|
||||||
const { nickname, ...rest } = params
|
|
||||||
return promisedRequest({
|
|
||||||
url: MASTODON_REGISTRATION_URL,
|
|
||||||
method: 'POST',
|
|
||||||
credentials,
|
|
||||||
payload: {
|
|
||||||
nickname,
|
|
||||||
locale: 'en_US',
|
|
||||||
agreement: true,
|
|
||||||
...rest,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCaptcha = () =>
|
|
||||||
promisedRequest({
|
|
||||||
url: '/api/pleroma/captcha',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const fetchUser = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: `${MASTODON_USER_URL}/${id}`,
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
|
|
||||||
|
|
||||||
export const fetchUserByName = ({ name, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_USER_LOOKUP_URL({ acct: name }),
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
.then(({ data }) => data.id)
|
|
||||||
.catch((error) => {
|
|
||||||
if (error?.statusCode === 404) {
|
|
||||||
// Either the backend does not support lookup endpoint,
|
|
||||||
// or there is no user with such name. Fallback and treat name as id.
|
|
||||||
return name
|
|
||||||
} else {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((id) => fetchUser({ id, credentials }))
|
|
||||||
|
|
||||||
export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_FOLLOWING_URL(id, {
|
|
||||||
maxId,
|
|
||||||
sinceId,
|
|
||||||
limit,
|
|
||||||
withRelationships: true,
|
|
||||||
}),
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
data: data.map(parseUser),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const fetchFollowers = ({
|
|
||||||
id,
|
|
||||||
maxId,
|
|
||||||
sinceId,
|
|
||||||
limit = 20,
|
|
||||||
credentials,
|
|
||||||
}) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_FOLLOWERS_URL(id, {
|
|
||||||
maxId,
|
|
||||||
sinceId,
|
|
||||||
limit,
|
|
||||||
withRelationships: true,
|
|
||||||
}),
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
data: data.map(parseUser),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const fetchConversation = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_STATUS_CONTEXT_URL(id),
|
|
||||||
credentials,
|
|
||||||
}).then((result) => ({
|
|
||||||
...result,
|
|
||||||
data: {
|
|
||||||
...result.data,
|
|
||||||
ancestors: result.data.ancestors.map(parseStatus),
|
|
||||||
descendants: result.data.descendants.map(parseStatus),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const fetchStatus = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_STATUS_URL(id),
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
|
||||||
|
|
||||||
export const fetchStatusSource = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_STATUS_SOURCE_URL(id),
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) }))
|
|
||||||
|
|
||||||
export const fetchStatusHistory = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_STATUS_HISTORY_URL(id),
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => {
|
|
||||||
return {
|
|
||||||
...rest,
|
|
||||||
data: [...data].reverse().map((item) => {
|
|
||||||
// History data is missing a lot of stuff present in original
|
|
||||||
// but we're really only missing the id for the timeago, the
|
|
||||||
// rest seem to render just fine.
|
|
||||||
item.id = id
|
|
||||||
return parseStatus(item)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const listEmojiPacks = ({ page, pageSize, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: EMOJI_PACKS_URL(page, pageSize),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const fetchPinnedStatuses = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_USER_TIMELINE_URL(id, { pinned: true }),
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseStatus) }))
|
|
||||||
|
|
||||||
export const verifyCredentials = ({ credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_LOGIN_URL,
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
|
|
||||||
|
|
||||||
export const resetPassword = ({ email }) => {
|
|
||||||
return promisedRequest({
|
|
||||||
url: MASTODON_PASSWORD_RESET_URL({ email }),
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const suggestions = ({ credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_SUGGESTIONS_URL,
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const fetchPoll = ({ pollId, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_POLL_URL(encodeURIComponent(pollId)),
|
|
||||||
method: 'GET',
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const fetchFavoritedByUsers = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_STATUS_FAVORITEDBY_URL(id),
|
|
||||||
method: 'GET',
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
|
||||||
|
|
||||||
export const fetchRebloggedByUsers = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_STATUS_REBLOGGEDBY_URL(id),
|
|
||||||
method: 'GET',
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
|
||||||
|
|
||||||
export const fetchEmojiReactions = ({ id, credentials }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: PLEROMA_EMOJI_REACTIONS_URL(id),
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
data: data.map((r) => {
|
|
||||||
r.accounts = r.accounts.map(parseUser)
|
|
||||||
return r
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const searchUsers = ({ credentials, query }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: MASTODON_USER_SEARCH_URL({ q: query, resolve: true }),
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
|
||||||
|
|
||||||
export const search2 = ({
|
|
||||||
credentials,
|
|
||||||
q,
|
|
||||||
resolve,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
following,
|
|
||||||
type,
|
|
||||||
}) => {
|
|
||||||
return promisedRequest({
|
|
||||||
url: MASTODON_SEARCH_2({
|
|
||||||
q,
|
|
||||||
resolve,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
following,
|
|
||||||
type,
|
|
||||||
withRelationships: true,
|
|
||||||
}),
|
|
||||||
credentials,
|
|
||||||
}).then(({ data, ...rest }) => {
|
|
||||||
data.accounts = data.accounts.slice(0, limit).map((u) => parseUser(u))
|
|
||||||
data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s))
|
|
||||||
return { ...rest, data }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchKnownDomains = ({ credentials }) =>
|
|
||||||
promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials })
|
|
||||||
|
|
||||||
export const fetchScrobbles = ({ accountId, limit = 1 }) =>
|
|
||||||
promisedRequest({
|
|
||||||
url: PLEROMA_SCROBBLES_URL(accountId, { limit }),
|
|
||||||
})
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
import { paramsString, promisedRequest } from './helpers.js'
|
|
||||||
|
|
||||||
import {
|
|
||||||
parseLinkHeaderPagination,
|
|
||||||
parseNotification,
|
|
||||||
parseStatus,
|
|
||||||
} from 'src/services/entity_normalizer/entity_normalizer.service.js'
|
|
||||||
|
|
||||||
const MASTODON_USER_HOME_TIMELINE_URL = ({
|
|
||||||
minId,
|
|
||||||
sinceId,
|
|
||||||
maxId,
|
|
||||||
limit,
|
|
||||||
replyVisibility,
|
|
||||||
}) =>
|
|
||||||
`/api/v1/timelines/home${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
|
||||||
const MASTODON_LIST_TIMELINE_URL = (
|
|
||||||
id,
|
|
||||||
{ minId, sinceId, maxId, limit, replyVisibility },
|
|
||||||
) =>
|
|
||||||
`/api/v1/timelines/list/${id}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
|
||||||
const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = ({
|
|
||||||
minId,
|
|
||||||
sinceId,
|
|
||||||
maxId,
|
|
||||||
limit,
|
|
||||||
replyVisibility,
|
|
||||||
}) =>
|
|
||||||
`/api/v1/timelines/direct${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
|
||||||
const MASTODON_PUBLIC_TIMELINE = ({
|
|
||||||
minId,
|
|
||||||
sinceId,
|
|
||||||
maxId,
|
|
||||||
limit,
|
|
||||||
replyVisibility,
|
|
||||||
local,
|
|
||||||
remote,
|
|
||||||
onlyMedia,
|
|
||||||
}) =>
|
|
||||||
`/api/v1/timelines/public${paramsString({ minId, sinceId, maxId, limit, replyVisibility, local, remote, onlyMedia })}`
|
|
||||||
const MASTODON_TAG_TIMELINE_URL = (
|
|
||||||
tag,
|
|
||||||
{ minId, sinceId, maxId, limit, replyVisibility },
|
|
||||||
) =>
|
|
||||||
`/api/v1/timelines/tag/${tag}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
|
||||||
export const MASTODON_USER_TIMELINE_URL = (
|
|
||||||
id,
|
|
||||||
{ minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia },
|
|
||||||
) =>
|
|
||||||
`/api/v1/accounts/${id}/statuses${paramsString({ minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia })}`
|
|
||||||
const MASTODON_USER_FAVORITES_TIMELINE_URL = ({
|
|
||||||
minId,
|
|
||||||
sinceId,
|
|
||||||
maxId,
|
|
||||||
limit,
|
|
||||||
replyVisibility,
|
|
||||||
}) =>
|
|
||||||
`/api/v1/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
|
||||||
const MASTODON_BOOKMARK_TIMELINE_URL = ({
|
|
||||||
minId,
|
|
||||||
sinceId,
|
|
||||||
maxId,
|
|
||||||
limit,
|
|
||||||
replyVisibility,
|
|
||||||
folderId,
|
|
||||||
}) =>
|
|
||||||
`/api/v1/bookmarks${paramsString({ minId, sinceId, maxId, limit, replyVisibility, folderId })}`
|
|
||||||
const PLEROMA_STATUS_QUOTES_URL = (
|
|
||||||
id,
|
|
||||||
{ minId, sinceId, maxId, limit, replyVisibility },
|
|
||||||
) =>
|
|
||||||
`/api/v1/pleroma/statuses/${id}/quotes${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
|
||||||
const PLEROMA_USER_FAVORITES_TIMELINE_URL = (
|
|
||||||
id,
|
|
||||||
{ minId, sinceId, maxId, limit, replyVisibility },
|
|
||||||
) =>
|
|
||||||
`/api/v1/pleroma/accounts/${id}/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
|
||||||
const AKKOMA_BUBBLE_TIMELINE_URL = ({
|
|
||||||
minId,
|
|
||||||
sinceId,
|
|
||||||
maxId,
|
|
||||||
limit,
|
|
||||||
replyVisibility,
|
|
||||||
}) =>
|
|
||||||
`/api/v1/timelines/bubble${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
|
||||||
|
|
||||||
const MASTODON_USER_NOTIFICATIONS_URL = ({
|
|
||||||
minId,
|
|
||||||
sinceId,
|
|
||||||
maxId,
|
|
||||||
limit,
|
|
||||||
includeTypes,
|
|
||||||
replyVisibility,
|
|
||||||
}) =>
|
|
||||||
`/api/v1/notifications${paramsString({ minId, sinceId, maxId, limit, includeTypes, replyVisibility })}`
|
|
||||||
|
|
||||||
export const fetchTimeline = ({
|
|
||||||
timeline,
|
|
||||||
credentials,
|
|
||||||
sinceId,
|
|
||||||
minId,
|
|
||||||
maxId,
|
|
||||||
userId,
|
|
||||||
listId,
|
|
||||||
statusId,
|
|
||||||
tag,
|
|
||||||
withMuted,
|
|
||||||
replyVisibility = 'all',
|
|
||||||
includeTypes = [],
|
|
||||||
bookmarkFolderId,
|
|
||||||
}) => {
|
|
||||||
const timelineUrls = {
|
|
||||||
friends: MASTODON_USER_HOME_TIMELINE_URL,
|
|
||||||
public: MASTODON_PUBLIC_TIMELINE,
|
|
||||||
publicAndExternal: MASTODON_PUBLIC_TIMELINE,
|
|
||||||
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
|
|
||||||
user: MASTODON_USER_TIMELINE_URL,
|
|
||||||
userPinned: MASTODON_USER_TIMELINE_URL,
|
|
||||||
media: MASTODON_USER_TIMELINE_URL,
|
|
||||||
list: MASTODON_LIST_TIMELINE_URL,
|
|
||||||
favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
|
|
||||||
publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL,
|
|
||||||
bookmarks: MASTODON_BOOKMARK_TIMELINE_URL,
|
|
||||||
bubble: AKKOMA_BUBBLE_TIMELINE_URL,
|
|
||||||
tag: MASTODON_TAG_TIMELINE_URL,
|
|
||||||
quotes: PLEROMA_STATUS_QUOTES_URL,
|
|
||||||
|
|
||||||
notifications: MASTODON_USER_NOTIFICATIONS_URL,
|
|
||||||
}
|
|
||||||
const urlFunc = timelineUrls[timeline]
|
|
||||||
|
|
||||||
const twoArgs = new Set([
|
|
||||||
'user',
|
|
||||||
'userPinned',
|
|
||||||
'media',
|
|
||||||
'list',
|
|
||||||
'publicFavorites',
|
|
||||||
'tag',
|
|
||||||
'quotes',
|
|
||||||
])
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
minId,
|
|
||||||
sinceId,
|
|
||||||
maxId,
|
|
||||||
limit: 20,
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = (() => {
|
|
||||||
switch (timeline) {
|
|
||||||
case 'user':
|
|
||||||
case 'userPinned':
|
|
||||||
case 'media':
|
|
||||||
return userId
|
|
||||||
case 'list':
|
|
||||||
return listId
|
|
||||||
case 'quotes':
|
|
||||||
return statusId
|
|
||||||
case 'tag':
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
const isNotifications = timeline === 'notifications'
|
|
||||||
|
|
||||||
if (timeline === 'media') {
|
|
||||||
params.onlyMedia = true
|
|
||||||
}
|
|
||||||
if (timeline === 'userPinned') {
|
|
||||||
params.pinned = true
|
|
||||||
}
|
|
||||||
if (timeline === 'public') {
|
|
||||||
params.local = true
|
|
||||||
}
|
|
||||||
if (timeline !== 'favorites' && timeline !== 'bookmarks') {
|
|
||||||
params.withMuted = withMuted
|
|
||||||
}
|
|
||||||
if (replyVisibility !== 'all') {
|
|
||||||
params.replyVisibility = replyVisibility
|
|
||||||
}
|
|
||||||
if (timeline === 'bookmarks' && bookmarkFolderId) {
|
|
||||||
params.folderId = bookmarkFolderId
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNotifications && includeTypes.length > 0) {
|
|
||||||
params.includeTypes = includeTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = twoArgs.has(timeline) ? urlFunc(id, params) : urlFunc(params)
|
|
||||||
return promisedRequest({ url, credentials }).then((result) => {
|
|
||||||
const pagination = parseLinkHeaderPagination(
|
|
||||||
result.response.headers.get('Link'),
|
|
||||||
{
|
|
||||||
flakeId: timeline !== 'bookmarks' && timeline !== 'notifications',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
...result,
|
|
||||||
data: result.data.map(isNotifications ? parseNotification : parseStatus),
|
|
||||||
pagination,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue