Add alt_text for posts. Fixes #1086 (#4477)

* Add alt_text for posts. Fixes #1086

* Moving alt_text to attachment name.

* Cleaning up mod action line.

* Addressing PR comments

* Addressing PR comments.

* Fixing clones.
This commit is contained in:
Dessalines 2024-03-05 05:34:57 -05:00 committed by GitHub
parent 52155c74cb
commit 7f9950fe85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 215 additions and 140 deletions

View File

@ -20,14 +20,14 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^20.11.19", "@types/node": "^20.11.22",
"@typescript-eslint/eslint-plugin": "^7.0.2", "@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.0.2", "@typescript-eslint/parser": "^7.1.0",
"download-file-sync": "^1.0.4", "download-file-sync": "^1.0.4",
"eslint": "^8.55.0", "eslint": "^8.57.0",
"eslint-plugin-prettier": "^5.0.1", "eslint-plugin-prettier": "^5.0.1",
"jest": "^29.5.0", "jest": "^29.5.0",
"lemmy-js-client": "0.19.4-alpha.4", "lemmy-js-client": "0.19.4-alpha.6",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"typescript": "^5.3.3" "typescript": "^5.3.3"

View File

@ -9,29 +9,29 @@ devDependencies:
specifier: ^29.5.12 specifier: ^29.5.12
version: 29.5.12 version: 29.5.12
'@types/node': '@types/node':
specifier: ^20.11.19 specifier: ^20.11.22
version: 20.11.19 version: 20.11.22
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^7.0.2 specifier: ^7.1.0
version: 7.0.2(@typescript-eslint/parser@7.0.2)(eslint@8.56.0)(typescript@5.3.3) version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^7.0.2 specifier: ^7.1.0
version: 7.0.2(eslint@8.56.0)(typescript@5.3.3) version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
download-file-sync: download-file-sync:
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4 version: 1.0.4
eslint: eslint:
specifier: ^8.55.0 specifier: ^8.57.0
version: 8.56.0 version: 8.57.0
eslint-plugin-prettier: eslint-plugin-prettier:
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.1.3(eslint@8.56.0)(prettier@3.2.5) version: 5.1.3(eslint@8.57.0)(prettier@3.2.5)
jest: jest:
specifier: ^29.5.0 specifier: ^29.5.0
version: 29.7.0(@types/node@20.11.19) version: 29.7.0(@types/node@20.11.22)
lemmy-js-client: lemmy-js-client:
specifier: 0.19.4-alpha.4 specifier: 0.19.4-alpha.6
version: 0.19.4-alpha.4 version: 0.19.4-alpha.6
prettier: prettier:
specifier: ^3.2.5 specifier: ^3.2.5
version: 3.2.5 version: 3.2.5
@ -386,13 +386,13 @@ packages:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true dev: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
dependencies: dependencies:
eslint: 8.56.0 eslint: 8.57.0
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
dev: true dev: true
@ -409,7 +409,7 @@ packages:
debug: 4.3.4 debug: 4.3.4
espree: 9.6.1 espree: 9.6.1
globals: 13.24.0 globals: 13.24.0
ignore: 5.3.0 ignore: 5.3.1
import-fresh: 3.3.0 import-fresh: 3.3.0
js-yaml: 4.1.0 js-yaml: 4.1.0
minimatch: 3.1.2 minimatch: 3.1.2
@ -418,8 +418,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@eslint/js@8.56.0: /@eslint/js@8.57.0:
resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
@ -464,7 +464,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
chalk: 4.1.2 chalk: 4.1.2
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@ -485,14 +485,14 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.9.0 ci-info: 3.9.0
exit: 0.1.2 exit: 0.1.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jest-changed-files: 29.7.0 jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@20.11.19) jest-config: 29.7.0(@types/node@20.11.22)
jest-haste-map: 29.7.0 jest-haste-map: 29.7.0
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-regex-util: 29.6.3 jest-regex-util: 29.6.3
@ -520,7 +520,7 @@ packages:
dependencies: dependencies:
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
jest-mock: 29.7.0 jest-mock: 29.7.0
dev: true dev: true
@ -547,7 +547,7 @@ packages:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0 '@sinonjs/fake-timers': 10.3.0
'@types/node': 20.11.19 '@types/node': 20.11.22
jest-message-util: 29.7.0 jest-message-util: 29.7.0
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
@ -580,7 +580,7 @@ packages:
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.22 '@jridgewell/trace-mapping': 0.3.22
'@types/node': 20.11.19 '@types/node': 20.11.22
chalk: 4.1.2 chalk: 4.1.2
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
exit: 0.1.2 exit: 0.1.2
@ -668,7 +668,7 @@ packages:
'@jest/schemas': 29.6.3 '@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4 '@types/istanbul-reports': 3.0.4
'@types/node': 20.11.19 '@types/node': 20.11.22
'@types/yargs': 17.0.32 '@types/yargs': 17.0.32
chalk: 4.1.2 chalk: 4.1.2
dev: true dev: true
@ -721,7 +721,7 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
dependencies: dependencies:
'@nodelib/fs.scandir': 2.1.5 '@nodelib/fs.scandir': 2.1.5
fastq: 1.17.0 fastq: 1.17.1
dev: true dev: true
/@pkgr/core@0.1.1: /@pkgr/core@0.1.1:
@ -777,7 +777,7 @@ packages:
/@types/graceful-fs@4.1.9: /@types/graceful-fs@4.1.9:
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
dependencies: dependencies:
'@types/node': 20.11.19 '@types/node': 20.11.22
dev: true dev: true
/@types/istanbul-lib-coverage@2.0.6: /@types/istanbul-lib-coverage@2.0.6:
@ -807,14 +807,14 @@ packages:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
dev: true dev: true
/@types/node@20.11.19: /@types/node@20.11.22:
resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==} resolution: {integrity: sha512-/G+IxWxma6V3E+pqK1tSl2Fo1kl41pK1yeCyDsgkF9WlVAme4j5ISYM2zR11bgLFJGLN5sVK40T4RJNuiZbEjA==}
dependencies: dependencies:
undici-types: 5.26.5 undici-types: 5.26.5
dev: true dev: true
/@types/semver@7.5.7: /@types/semver@7.5.8:
resolution: {integrity: sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==} resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
dev: true dev: true
/@types/stack-utils@2.0.3: /@types/stack-utils@2.0.3:
@ -831,8 +831,8 @@ packages:
'@types/yargs-parser': 21.0.3 '@types/yargs-parser': 21.0.3
dev: true dev: true
/@typescript-eslint/eslint-plugin@7.0.2(@typescript-eslint/parser@7.0.2)(eslint@8.56.0)(typescript@5.3.3): /@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3):
resolution: {integrity: sha512-/XtVZJtbaphtdrWjr+CJclaCVGPtOdBpFEnvtNf/jRV0IiEemRrL0qABex/nEt8isYcnFacm3nPHYQwL+Wb7qg==} resolution: {integrity: sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': ^7.0.0 '@typescript-eslint/parser': ^7.0.0
@ -843,13 +843,13 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@eslint-community/regexpp': 4.10.0 '@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 7.0.2(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
'@typescript-eslint/scope-manager': 7.0.2 '@typescript-eslint/scope-manager': 7.1.0
'@typescript-eslint/type-utils': 7.0.2(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/type-utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
'@typescript-eslint/utils': 7.0.2(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 7.0.2 '@typescript-eslint/visitor-keys': 7.1.0
debug: 4.3.4 debug: 4.3.4
eslint: 8.56.0 eslint: 8.57.0
graphemer: 1.4.0 graphemer: 1.4.0
ignore: 5.3.1 ignore: 5.3.1
natural-compare: 1.4.0 natural-compare: 1.4.0
@ -860,8 +860,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/parser@7.0.2(eslint@8.56.0)(typescript@5.3.3): /@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3):
resolution: {integrity: sha512-GdwfDglCxSmU+QTS9vhz2Sop46ebNCXpPPvsByK7hu0rFGRHL+AusKQJ7SoN+LbLh6APFpQwHKmDSwN35Z700Q==} resolution: {integrity: sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
@ -870,27 +870,27 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/scope-manager': 7.0.2 '@typescript-eslint/scope-manager': 7.1.0
'@typescript-eslint/types': 7.0.2 '@typescript-eslint/types': 7.1.0
'@typescript-eslint/typescript-estree': 7.0.2(typescript@5.3.3) '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 7.0.2 '@typescript-eslint/visitor-keys': 7.1.0
debug: 4.3.4 debug: 4.3.4
eslint: 8.56.0 eslint: 8.57.0
typescript: 5.3.3 typescript: 5.3.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/scope-manager@7.0.2: /@typescript-eslint/scope-manager@7.1.0:
resolution: {integrity: sha512-l6sa2jF3h+qgN2qUMjVR3uCNGjWw4ahGfzIYsCtFrQJCjhbrDPdiihYT8FnnqFwsWX+20hK592yX9I2rxKTP4g==} resolution: {integrity: sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 7.0.2 '@typescript-eslint/types': 7.1.0
'@typescript-eslint/visitor-keys': 7.0.2 '@typescript-eslint/visitor-keys': 7.1.0
dev: true dev: true
/@typescript-eslint/type-utils@7.0.2(eslint@8.56.0)(typescript@5.3.3): /@typescript-eslint/type-utils@7.1.0(eslint@8.57.0)(typescript@5.3.3):
resolution: {integrity: sha512-IKKDcFsKAYlk8Rs4wiFfEwJTQlHcdn8CLwLaxwd6zb8HNiMcQIFX9sWax2k4Cjj7l7mGS5N1zl7RCHOVwHq2VQ==} resolution: {integrity: sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
@ -899,23 +899,23 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 7.0.2(typescript@5.3.3) '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
'@typescript-eslint/utils': 7.0.2(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
debug: 4.3.4 debug: 4.3.4
eslint: 8.56.0 eslint: 8.57.0
ts-api-utils: 1.2.1(typescript@5.3.3) ts-api-utils: 1.2.1(typescript@5.3.3)
typescript: 5.3.3 typescript: 5.3.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/types@7.0.2: /@typescript-eslint/types@7.1.0:
resolution: {integrity: sha512-ZzcCQHj4JaXFjdOql6adYV4B/oFOFjPOC9XYwCaZFRvqN8Llfvv4gSxrkQkd2u4Ci62i2c6W6gkDwQJDaRc4nA==} resolution: {integrity: sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
dev: true dev: true
/@typescript-eslint/typescript-estree@7.0.2(typescript@5.3.3): /@typescript-eslint/typescript-estree@7.1.0(typescript@5.3.3):
resolution: {integrity: sha512-3AMc8khTcELFWcKcPc0xiLviEvvfzATpdPj/DXuOGIdQIIFybf4DMT1vKRbuAEOFMwhWt7NFLXRkbjsvKZQyvw==} resolution: {integrity: sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@ -923,8 +923,8 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/types': 7.0.2 '@typescript-eslint/types': 7.1.0
'@typescript-eslint/visitor-keys': 7.0.2 '@typescript-eslint/visitor-keys': 7.1.0
debug: 4.3.4 debug: 4.3.4
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
@ -936,30 +936,30 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/utils@7.0.2(eslint@8.56.0)(typescript@5.3.3): /@typescript-eslint/utils@7.1.0(eslint@8.57.0)(typescript@5.3.3):
resolution: {integrity: sha512-PZPIONBIB/X684bhT1XlrkjNZJIEevwkKDsdwfiu1WeqBxYEEdIgVDgm8/bbKHVu+6YOpeRqcfImTdImx/4Bsw==} resolution: {integrity: sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/json-schema': 7.0.15 '@types/json-schema': 7.0.15
'@types/semver': 7.5.7 '@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 7.0.2 '@typescript-eslint/scope-manager': 7.1.0
'@typescript-eslint/types': 7.0.2 '@typescript-eslint/types': 7.1.0
'@typescript-eslint/typescript-estree': 7.0.2(typescript@5.3.3) '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
eslint: 8.56.0 eslint: 8.57.0
semver: 7.6.0 semver: 7.6.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
dev: true dev: true
/@typescript-eslint/visitor-keys@7.0.2: /@typescript-eslint/visitor-keys@7.1.0:
resolution: {integrity: sha512-8Y+YiBmqPighbm5xA2k4wKTxRzx9EkBu7Rlw+WHqMvRJ3RPz/BMBO9b2ru0LUNmXg120PHUXD5+SWFy2R8DqlQ==} resolution: {integrity: sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==}
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 7.0.2 '@typescript-eslint/types': 7.1.0
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
dev: true dev: true
@ -1276,7 +1276,7 @@ packages:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
dev: true dev: true
/create-jest@29.7.0(@types/node@20.11.19): /create-jest@29.7.0(@types/node@20.11.22):
resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true hasBin: true
@ -1285,7 +1285,7 @@ packages:
chalk: 4.1.2 chalk: 4.1.2
exit: 0.1.2 exit: 0.1.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@20.11.19) jest-config: 29.7.0(@types/node@20.11.22)
jest-util: 29.7.0 jest-util: 29.7.0
prompts: 2.4.2 prompts: 2.4.2
transitivePeerDependencies: transitivePeerDependencies:
@ -1414,7 +1414,7 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/eslint-plugin-prettier@5.1.3(eslint@8.56.0)(prettier@3.2.5): /eslint-plugin-prettier@5.1.3(eslint@8.57.0)(prettier@3.2.5):
resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -1428,7 +1428,7 @@ packages:
eslint-config-prettier: eslint-config-prettier:
optional: true optional: true
dependencies: dependencies:
eslint: 8.56.0 eslint: 8.57.0
prettier: 3.2.5 prettier: 3.2.5
prettier-linter-helpers: 1.0.0 prettier-linter-helpers: 1.0.0
synckit: 0.8.8 synckit: 0.8.8
@ -1447,15 +1447,15 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/eslint@8.56.0: /eslint@8.57.0:
resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true hasBin: true
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@eslint-community/regexpp': 4.10.0 '@eslint-community/regexpp': 4.10.0
'@eslint/eslintrc': 2.1.4 '@eslint/eslintrc': 2.1.4
'@eslint/js': 8.56.0 '@eslint/js': 8.57.0
'@humanwhocodes/config-array': 0.11.14 '@humanwhocodes/config-array': 0.11.14
'@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8 '@nodelib/fs.walk': 1.2.8
@ -1477,7 +1477,7 @@ packages:
glob-parent: 6.0.2 glob-parent: 6.0.2
globals: 13.24.0 globals: 13.24.0
graphemer: 1.4.0 graphemer: 1.4.0
ignore: 5.3.0 ignore: 5.3.1
imurmurhash: 0.1.4 imurmurhash: 0.1.4
is-glob: 4.0.3 is-glob: 4.0.3
is-path-inside: 3.0.3 is-path-inside: 3.0.3
@ -1591,8 +1591,8 @@ packages:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
dev: true dev: true
/fastq@1.17.0: /fastq@1.17.1:
resolution: {integrity: sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==} resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
dependencies: dependencies:
reusify: 1.0.4 reusify: 1.0.4
dev: true dev: true
@ -1637,13 +1637,13 @@ packages:
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
dependencies: dependencies:
flatted: 3.2.9 flatted: 3.3.1
keyv: 4.5.4 keyv: 4.5.4
rimraf: 3.0.2 rimraf: 3.0.2
dev: true dev: true
/flatted@3.2.9: /flatted@3.3.1:
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
dev: true dev: true
/form-data@4.0.0: /form-data@4.0.0:
@ -1774,11 +1774,6 @@ packages:
engines: {node: '>=10.17.0'} engines: {node: '>=10.17.0'}
dev: true dev: true
/ignore@5.3.0:
resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==}
engines: {node: '>= 4'}
dev: true
/ignore@5.3.1: /ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@ -1944,7 +1939,7 @@ packages:
'@jest/expect': 29.7.0 '@jest/expect': 29.7.0
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
chalk: 4.1.2 chalk: 4.1.2
co: 4.6.0 co: 4.6.0
dedent: 1.5.1 dedent: 1.5.1
@ -1965,7 +1960,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/jest-cli@29.7.0(@types/node@20.11.19): /jest-cli@29.7.0(@types/node@20.11.22):
resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true hasBin: true
@ -1979,10 +1974,10 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
chalk: 4.1.2 chalk: 4.1.2
create-jest: 29.7.0(@types/node@20.11.19) create-jest: 29.7.0(@types/node@20.11.22)
exit: 0.1.2 exit: 0.1.2
import-local: 3.1.0 import-local: 3.1.0
jest-config: 29.7.0(@types/node@20.11.19) jest-config: 29.7.0(@types/node@20.11.22)
jest-util: 29.7.0 jest-util: 29.7.0
jest-validate: 29.7.0 jest-validate: 29.7.0
yargs: 17.7.2 yargs: 17.7.2
@ -1993,7 +1988,7 @@ packages:
- ts-node - ts-node
dev: true dev: true
/jest-config@29.7.0(@types/node@20.11.19): /jest-config@29.7.0(@types/node@20.11.22):
resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies: peerDependencies:
@ -2008,7 +2003,7 @@ packages:
'@babel/core': 7.23.9 '@babel/core': 7.23.9
'@jest/test-sequencer': 29.7.0 '@jest/test-sequencer': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
babel-jest: 29.7.0(@babel/core@7.23.9) babel-jest: 29.7.0(@babel/core@7.23.9)
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.9.0 ci-info: 3.9.0
@ -2068,7 +2063,7 @@ packages:
'@jest/environment': 29.7.0 '@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0 '@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
jest-mock: 29.7.0 jest-mock: 29.7.0
jest-util: 29.7.0 jest-util: 29.7.0
dev: true dev: true
@ -2084,7 +2079,7 @@ packages:
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9 '@types/graceful-fs': 4.1.9
'@types/node': 20.11.19 '@types/node': 20.11.22
anymatch: 3.1.3 anymatch: 3.1.3
fb-watchman: 2.0.2 fb-watchman: 2.0.2
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -2135,7 +2130,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
jest-util: 29.7.0 jest-util: 29.7.0
dev: true dev: true
@ -2190,7 +2185,7 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -2221,7 +2216,7 @@ packages:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/transform': 29.7.0 '@jest/transform': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
chalk: 4.1.2 chalk: 4.1.2
cjs-module-lexer: 1.2.3 cjs-module-lexer: 1.2.3
collect-v8-coverage: 1.0.2 collect-v8-coverage: 1.0.2
@ -2273,7 +2268,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
chalk: 4.1.2 chalk: 4.1.2
ci-info: 3.9.0 ci-info: 3.9.0
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -2298,7 +2293,7 @@ packages:
dependencies: dependencies:
'@jest/test-result': 29.7.0 '@jest/test-result': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
'@types/node': 20.11.19 '@types/node': 20.11.22
ansi-escapes: 4.3.2 ansi-escapes: 4.3.2
chalk: 4.1.2 chalk: 4.1.2
emittery: 0.13.1 emittery: 0.13.1
@ -2310,13 +2305,13 @@ packages:
resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies: dependencies:
'@types/node': 20.11.19 '@types/node': 20.11.22
jest-util: 29.7.0 jest-util: 29.7.0
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1
dev: true dev: true
/jest@29.7.0(@types/node@20.11.19): /jest@29.7.0(@types/node@20.11.22):
resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true hasBin: true
@ -2329,7 +2324,7 @@ packages:
'@jest/core': 29.7.0 '@jest/core': 29.7.0
'@jest/types': 29.6.3 '@jest/types': 29.6.3
import-local: 3.1.0 import-local: 3.1.0
jest-cli: 29.7.0(@types/node@20.11.19) jest-cli: 29.7.0(@types/node@20.11.22)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- babel-plugin-macros - babel-plugin-macros
@ -2395,8 +2390,8 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/lemmy-js-client@0.19.4-alpha.4: /lemmy-js-client@0.19.4-alpha.6:
resolution: {integrity: sha512-ZsE/1dRiNAzOjGNtyv+1JH7AzKWBkHY4FH4EGrdhkKtNY7EKDWt4uEkc3Bde+cQg4YTdbzXyuY94gAVCFm+L1w==} resolution: {integrity: sha512-x4htMlpoZ7hzrhrIk82aompVxbpu2ZDWtmWNGraM0+27nUCDf6gYxJH5nb5R/o39BQe5KSHq6zoBdliBwAY40w==}
dependencies: dependencies:
cross-fetch: 4.0.0 cross-fetch: 4.0.0
form-data: 4.0.0 form-data: 4.0.0
@ -2994,7 +2989,7 @@ packages:
'@babel/core': 7.23.9 '@babel/core': 7.23.9
bs-logger: 0.2.6 bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0 fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@20.11.19) jest: 29.7.0(@types/node@20.11.22)
jest-util: 29.7.0 jest-util: 29.7.0
json5: 2.2.3 json5: 2.2.3
lodash.memoize: 4.1.2 lodash.memoize: 4.1.2

View File

@ -202,4 +202,7 @@ test("No image proxying if setting is disabled", async () => {
gammaPost.post!.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"), gammaPost.post!.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
).toBeTruthy(); ).toBeTruthy();
expect(gammaPost.post!.post.body).toBe("![](http://example.com/image2.png)"); expect(gammaPost.post!.post.body).toBe("![](http://example.com/image2.png)");
// Make sure the alt text got federated
expect(post.post_view.post.alt_text).toBe(gammaPost.post!.post.alt_text);
}); });

View File

@ -200,11 +200,13 @@ export async function createPost(
body = randomString(10), body = randomString(10),
// use example.com for consistent title and embed description // use example.com for consistent title and embed description
name: string = randomString(5), name: string = randomString(5),
alt_text = randomString(10),
): Promise<PostResponse> { ): Promise<PostResponse> {
let form: CreatePost = { let form: CreatePost = {
name, name,
url, url,
body, body,
alt_text,
community_id, community_id,
}; };
return api.createPost(form); return api.createPost(form);

View File

@ -24,6 +24,8 @@ pub struct CreatePost {
pub url: Option<Url>, pub url: Option<Url>,
/// An optional body for the post in markdown. /// An optional body for the post in markdown.
pub body: Option<String>, pub body: Option<String>,
/// An optional alt_text, usable for image posts.
pub alt_text: Option<String>,
/// A honeypot to catch bots. Should be None. /// A honeypot to catch bots. Should be None.
pub honeypot: Option<String>, pub honeypot: Option<String>,
pub nsfw: Option<bool>, pub nsfw: Option<bool>,
@ -116,6 +118,8 @@ pub struct EditPost {
pub url: Option<Url>, pub url: Option<Url>,
/// An optional body for the post in markdown. /// An optional body for the post in markdown.
pub body: Option<String>, pub body: Option<String>,
/// An optional alt_text, usable for image posts.
pub alt_text: Option<String>,
pub nsfw: Option<bool>, pub nsfw: Option<bool>,
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
#[cfg_attr(feature = "full", ts(type = "string"))] #[cfg_attr(feature = "full", ts(type = "string"))]

View File

@ -35,7 +35,13 @@ use lemmy_utils::{
spawn_try_task, spawn_try_task,
utils::{ utils::{
slurs::check_slurs, slurs::check_slurs,
validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title}, validation::{
check_url_scheme,
clean_url_params,
is_valid_alt_text_field,
is_valid_body_field,
is_valid_post_title,
},
}, },
}; };
use tracing::Instrument; use tracing::Instrument;
@ -50,17 +56,19 @@ pub async fn create_post(
) -> Result<Json<PostResponse>, LemmyError> { ) -> Result<Json<PostResponse>, LemmyError> {
let local_site = LocalSite::read(&mut context.pool()).await?; let local_site = LocalSite::read(&mut context.pool()).await?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.name, &slur_regex)?;
let body = process_markdown_opt(&data.body, &slur_regex, &context).await?;
honeypot_check(&data.honeypot)?; honeypot_check(&data.honeypot)?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.name, &slur_regex)?;
let body = process_markdown_opt(&data.body, &slur_regex, &context).await?;
let data_url = data.url.as_ref(); let data_url = data.url.as_ref();
let url = data_url.map(clean_url_params); // TODO no good way to handle a "clear" let url = data_url.map(clean_url_params); // TODO no good way to handle a "clear"
let custom_thumbnail = data.custom_thumbnail.as_ref().map(clean_url_params); let custom_thumbnail = data.custom_thumbnail.as_ref().map(clean_url_params);
is_valid_post_title(&data.name)?; is_valid_post_title(&data.name)?;
is_valid_body_field(&body, true)?; is_valid_body_field(&body, true)?;
is_valid_alt_text_field(&data.alt_text)?;
check_url_scheme(&url)?; check_url_scheme(&url)?;
check_url_scheme(&custom_thumbnail)?; check_url_scheme(&custom_thumbnail)?;
@ -122,8 +130,10 @@ pub async fn create_post(
let post_form = PostInsertForm::builder() let post_form = PostInsertForm::builder()
.name(data.name.trim().to_string()) .name(data.name.trim().to_string())
.url_content_type(metadata.content_type)
.url(url) .url(url)
.body(body) .body(body)
.alt_text(data.alt_text.clone())
.community_id(data.community_id) .community_id(data.community_id)
.creator_id(local_user_view.person.id) .creator_id(local_user_view.person.id)
.nsfw(data.nsfw) .nsfw(data.nsfw)

View File

@ -27,7 +27,13 @@ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType}, error::{LemmyError, LemmyErrorExt, LemmyErrorType},
utils::{ utils::{
slurs::check_slurs_opt, slurs::check_slurs_opt,
validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title}, validation::{
check_url_scheme,
clean_url_params,
is_valid_alt_text_field,
is_valid_body_field,
is_valid_post_title,
},
}, },
}; };
use std::ops::Deref; use std::ops::Deref;
@ -54,6 +60,7 @@ pub async fn update_post(
} }
is_valid_body_field(&body, true)?; is_valid_body_field(&body, true)?;
is_valid_alt_text_field(&data.alt_text)?;
check_url_scheme(&url)?; check_url_scheme(&url)?;
check_url_scheme(&custom_thumbnail)?; check_url_scheme(&custom_thumbnail)?;
@ -73,11 +80,12 @@ pub async fn update_post(
} }
// Fetch post links and thumbnail if url was updated // Fetch post links and thumbnail if url was updated
let (embed_title, embed_description, embed_video_url, metadata_thumbnail) = match &url { let (embed_title, embed_description, embed_video_url, metadata_thumbnail, metadata_content_type) =
match &url {
Some(url) => { Some(url) => {
// Only generate the thumbnail if there's no custom thumbnail provided, // Only generate the thumbnail if there's no custom thumbnail provided,
// otherwise it will save it in pictrs // otherwise it will save it in pictrs
let generate_thumbnail = custom_thumbnail.is_none(); let generate_thumbnail = custom_thumbnail.is_none() || orig_post.thumbnail_url.is_none();
let metadata = fetch_link_metadata(url, generate_thumbnail, &context).await?; let metadata = fetch_link_metadata(url, generate_thumbnail, &context).await?;
( (
@ -85,6 +93,7 @@ pub async fn update_post(
Some(metadata.opengraph_data.description), Some(metadata.opengraph_data.description),
Some(metadata.opengraph_data.embed_video_url), Some(metadata.opengraph_data.embed_video_url),
Some(metadata.thumbnail), Some(metadata.thumbnail),
Some(metadata.content_type),
) )
} }
_ => Default::default(), _ => Default::default(),
@ -115,7 +124,9 @@ pub async fn update_post(
let post_form = PostUpdateForm { let post_form = PostUpdateForm {
name: data.name.clone(), name: data.name.clone(),
url, url,
url_content_type: metadata_content_type,
body: diesel_option_overwrite(body), body: diesel_option_overwrite(body),
alt_text: diesel_option_overwrite(data.alt_text.clone()),
nsfw: data.nsfw, nsfw: data.nsfw,
embed_title, embed_title,
embed_description, embed_description,

View File

@ -115,7 +115,13 @@ impl Object for ApubPost {
let attachment = self let attachment = self
.url .url
.clone() .clone()
.map(|url| Attachment::new(url.into(), self.url_content_type.clone())) .map(|url| {
Attachment::new(
url.into(),
self.url_content_type.clone(),
self.alt_text.clone(),
)
})
.into_iter() .into_iter()
.collect(); .collect();
@ -203,10 +209,11 @@ impl Object for ApubPost {
// read existing, local post if any (for generating mod log) // read existing, local post if any (for generating mod log)
let old_post = page.id.dereference_local(context).await; let old_post = page.id.dereference_local(context).await;
let first_attachment = page.attachment.first();
let form = if !page.is_mod_action(context).await? { let form = if !page.is_mod_action(context).await? {
let first_attachment = page.attachment.into_iter().map(Attachment::url).next(); let url = if let Some(attachment) = first_attachment.cloned() {
let url = if first_attachment.is_some() { Some(attachment.url())
first_attachment
} else if page.kind == PageType::Video { } else if page.kind == PageType::Video {
// we cant display videos directly, so insert a link to external video page // we cant display videos directly, so insert a link to external video page
Some(page.id.inner().clone()) Some(page.id.inner().clone())
@ -215,6 +222,7 @@ impl Object for ApubPost {
}; };
check_url_scheme(&url)?; check_url_scheme(&url)?;
let alt_text = first_attachment.cloned().and_then(Attachment::alt_text);
let local_site = LocalSite::read(&mut context.pool()).await.ok(); let local_site = LocalSite::read(&mut context.pool()).await.ok();
let allow_sensitive = local_site_opt_to_sensitive(&local_site); let allow_sensitive = local_site_opt_to_sensitive(&local_site);
let page_is_sensitive = page.sensitive.unwrap_or(false); let page_is_sensitive = page.sensitive.unwrap_or(false);
@ -241,6 +249,7 @@ impl Object for ApubPost {
name, name,
url: url.map(Into::into), url: url.map(Into::into),
body, body,
alt_text,
creator_id: creator.id, creator_id: creator.id,
community_id: community.id, community_id: community.id,
removed: None, removed: None,

View File

@ -82,6 +82,8 @@ pub(crate) struct Image {
#[serde(rename = "type")] #[serde(rename = "type")]
kind: ImageType, kind: ImageType,
url: Url, url: Url,
/// Used for alt_text
name: Option<String>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -90,6 +92,8 @@ pub(crate) struct Document {
#[serde(rename = "type")] #[serde(rename = "type")]
kind: DocumentType, kind: DocumentType,
url: Url, url: Url,
/// Used for alt_text
name: Option<String>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -111,6 +115,14 @@ impl Attachment {
Attachment::Document(d) => d.url, Attachment::Document(d) => d.url,
} }
} }
pub(crate) fn alt_text(self) -> Option<String> {
match self {
Attachment::Image(i) => i.name,
Attachment::Document(d) => d.name,
_ => None,
}
}
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -168,12 +180,13 @@ impl Page {
impl Attachment { impl Attachment {
/// Creates new attachment for a given link and mime type. /// Creates new attachment for a given link and mime type.
pub(crate) fn new(url: Url, media_type: Option<String>) -> Attachment { pub(crate) fn new(url: Url, media_type: Option<String>, alt_text: Option<String>) -> Attachment {
let is_image = media_type.clone().unwrap_or_default().starts_with("image"); let is_image = media_type.clone().unwrap_or_default().starts_with("image");
if is_image { if is_image {
Attachment::Image(Image { Attachment::Image(Image {
kind: Default::default(), kind: Default::default(),
url, url,
name: alt_text,
}) })
} else { } else {
Attachment::Link(Link { Attachment::Link(Link {

View File

@ -434,6 +434,7 @@ mod tests {
name: "A test post".into(), name: "A test post".into(),
url: None, url: None,
body: None, body: None,
alt_text: None,
creator_id: inserted_person.id, creator_id: inserted_person.id,
community_id: inserted_community.id, community_id: inserted_community.id,
published: inserted_post.published, published: inserted_post.published,

View File

@ -705,6 +705,7 @@ diesel::table! {
featured_community -> Bool, featured_community -> Bool,
featured_local -> Bool, featured_local -> Bool,
url_content_type -> Nullable<Text>, url_content_type -> Nullable<Text>,
alt_text -> Nullable<Text>,
} }
} }

View File

@ -56,6 +56,8 @@ pub struct Post {
/// Whether the post is featured to its site. /// Whether the post is featured to its site.
pub featured_local: bool, pub featured_local: bool,
pub url_content_type: Option<String>, pub url_content_type: Option<String>,
/// An optional alt_text, usable for image posts.
pub alt_text: Option<String>,
} }
#[derive(Debug, Clone, TypedBuilder)] #[derive(Debug, Clone, TypedBuilder)]
@ -87,6 +89,7 @@ pub struct PostInsertForm {
pub featured_community: Option<bool>, pub featured_community: Option<bool>,
pub featured_local: Option<bool>, pub featured_local: Option<bool>,
pub url_content_type: Option<String>, pub url_content_type: Option<String>,
pub alt_text: Option<String>,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -111,7 +114,8 @@ pub struct PostUpdateForm {
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
pub featured_community: Option<bool>, pub featured_community: Option<bool>,
pub featured_local: Option<bool>, pub featured_local: Option<bool>,
pub url_content_type: Option<String>, pub url_content_type: Option<Option<String>>,
pub alt_text: Option<Option<String>>,
} }
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]

View File

@ -1063,6 +1063,7 @@ mod tests {
creator_id: data.timmy_local_user_view.person.id, creator_id: data.timmy_local_user_view.person.id,
url: None, url: None,
body: None, body: None,
alt_text: None,
published: data.inserted_post.published, published: data.inserted_post.published,
updated: None, updated: None,
community_id: data.inserted_community.id, community_id: data.inserted_community.id,

View File

@ -1556,6 +1556,7 @@ mod tests {
creator_id: inserted_person.id, creator_id: inserted_person.id,
url: None, url: None,
body: None, body: None,
alt_text: None,
published: inserted_post.published, published: inserted_post.published,
updated: None, updated: None,
community_id: inserted_community.id, community_id: inserted_community.id,

View File

@ -101,6 +101,7 @@ pub enum LemmyErrorType {
InvalidPostTitle, InvalidPostTitle,
InvalidBodyField, InvalidBodyField,
BioLengthOverflow, BioLengthOverflow,
AltTextLengthOverflow,
MissingTotpToken, MissingTotpToken,
MissingTotpSecret, MissingTotpSecret,
IncorrectTotpToken, IncorrectTotpToken,

View File

@ -19,6 +19,7 @@ const ALLOWED_POST_URL_SCHEMES: [&str; 3] = ["http", "https", "magnet"];
const BODY_MAX_LENGTH: usize = 10000; const BODY_MAX_LENGTH: usize = 10000;
const POST_BODY_MAX_LENGTH: usize = 50000; const POST_BODY_MAX_LENGTH: usize = 50000;
const BIO_MAX_LENGTH: usize = 300; const BIO_MAX_LENGTH: usize = 300;
const ALT_TEXT_MAX_LENGTH: usize = 300;
const SITE_NAME_MAX_LENGTH: usize = 20; const SITE_NAME_MAX_LENGTH: usize = 20;
const SITE_NAME_MIN_LENGTH: usize = 1; const SITE_NAME_MIN_LENGTH: usize = 1;
const SITE_DESCRIPTION_MAX_LENGTH: usize = 150; const SITE_DESCRIPTION_MAX_LENGTH: usize = 150;
@ -172,6 +173,18 @@ pub fn is_valid_bio_field(bio: &str) -> LemmyResult<()> {
max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow) max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow)
} }
pub fn is_valid_alt_text_field(alt_text: &Option<String>) -> LemmyResult<()> {
if let Some(alt_text) = alt_text {
max_length_check(
alt_text,
ALT_TEXT_MAX_LENGTH,
LemmyErrorType::AltTextLengthOverflow,
)
} else {
Ok(())
}
}
/// Checks the site name length, the limit as defined in the DB. /// Checks the site name length, the limit as defined in the DB.
pub fn site_name_length_check(name: &str) -> LemmyResult<()> { pub fn site_name_length_check(name: &str) -> LemmyResult<()> {
min_length_check(name, SITE_NAME_MIN_LENGTH, LemmyErrorType::SiteNameRequired)?; min_length_check(name, SITE_NAME_MIN_LENGTH, LemmyErrorType::SiteNameRequired)?;

View File

@ -0,0 +1,3 @@
ALTER TABLE post
DROP COLUMN alt_text;

View File

@ -0,0 +1,3 @@
ALTER TABLE post
ADD COLUMN alt_text text;