Merge pull request #1807 from pixelfed/staging

Staging
This commit is contained in:
daniel 2019-11-10 19:55:49 -07:00 committed by GitHub
commit 3b52f809f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1079 additions and 602 deletions

View File

@ -41,7 +41,8 @@ jobs:
paths:
- vendor
- run: cp .env.example .env
- run: cp .env.testing .env
- run: php artisan route:clear
- run: php artisan storage:link
- run: php artisan key:generate
- run: php artisan config:clear

View File

@ -37,6 +37,10 @@
- Updated StatusHashtagService, reduce cached hashtag count ttl from 6 hours to 5 minutes ([126886e8](https://github.com/pixelfed/pixelfed/commit/126886e8))
- Updated Hashtag.vue component, added formatted posts count ([c71f3dd1](https://github.com/pixelfed/pixelfed/commit/c71f3dd1))
- Updated FixLikes command, fix postgres support ([771f9c46](https://github.com/pixelfed/pixelfed/commit/771f9c46))
- Updated Settings, hide sponsors feature until re-implemented in Profile UI ([c4dd8449](https://github.com/pixelfed/pixelfed/commit/c4dd8449))
- Updated Status view, added ```video``` open graph tag support ([#1799](https://github.com/pixelfed/pixelfed/pull/1799))
- Updated AccountTransformer, added ```local``` attribute ([d2a90f11](https://github.com/pixelfed/pixelfed/commit/d2a90f11))
- Updated Laravel framework from v5.8 to v6.x ([3aff6de33](https://github.com/pixelfed/pixelfed/commit/3aff6de33))
## Deprecated

View File

@ -10,8 +10,13 @@ Remember, bug reports are created in the hope that others with the same problem
## Core Development Discussion
Informal discussion regarding bugs, new features, and implementation of existing features takes place in the ```#pixelfed-dev``` channel on the Freenode IRC network.
## Branches
If you want to contribute to this repository, please file your pull request against the `staging` branch.
Pixelfed Beta currently uses the `dev` branch for deployable code. When v1.0 is released, the stable branch will be changed to `master`, with `dev` branch being used for development and testing.
## Compiled Assets
If you are submitting a change that will affect a compiled file, such as most of the files in ```resources/assets/sass``` or ```resources/assets/js``` of the pixelfed/pixelfed repository, do not commit the compiled files. Due to their large size, they cannot realistically be reviewed by a maintainer. This could be exploited as a way to inject malicious code into Pixelfed. In order to defensively prevent this, all compiled files will be generated and committed by Pixelfed maintainers.
## Security Vulnerabilities
If you discover a security vulnerability within Pixelfed, please send an email to Daniel Supernault at hello@pixelfed.org. All security vulnerabilities will be promptly addressed.
If you discover a security vulnerability within Pixelfed, please send an email to Daniel Supernault at hello@pixelfed.org. All security vulnerabilities will be promptly addressed.

View File

@ -1 +0,0 @@
contrib/docker/Dockerfile.apache

View File

@ -27,7 +27,7 @@ class AuthServiceProvider extends ServiceProvider
$this->registerPolicies();
if(config('pixelfed.oauth_enabled')) {
Passport::routes(null, ['middleware' => [ \Barryvdh\Cors\HandleCors::class ]]);
Passport::routes(null, ['middleware' => ['twofactor', \Barryvdh\Cors\HandleCors::class]]);
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
Passport::enableImplicitGrant();

View File

@ -4,9 +4,26 @@ namespace App;
use Auth;
use Illuminate\Database\Eloquent\Model;
use Pixelfed\Snowflake\HasSnowflakePrimary;
class Story extends Model
{
use HasSnowflakePrimary;
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = false;
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = ['published_at', 'expires_at'];
protected $visible = ['id'];
public function profile()

View File

@ -3,10 +3,29 @@
namespace App;
use Illuminate\Database\Eloquent\Model;
use Pixelfed\Snowflake\HasSnowflakePrimary;
use Storage;
class StoryItem extends Model
{
use HasSnowflakePrimary;
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = false;
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = ['expires_at'];
protected $visible = ['id'];
public function story()
{
return $this->belongsTo(Story::class);
@ -14,6 +33,6 @@ class StoryItem extends Model
public function url()
{
return Storage::url($this->media_path);
return url(Storage::url($this->media_path));
}
}

View File

@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Model;
class StoryView extends Model
{
public $fillable = ['story_id', 'profile_id'];
public function story()
{
return $this->belongsTo(Story::class);

View File

@ -31,6 +31,7 @@ class AccountTransformer extends Fractal\TransformerAbstract
'url' => $profile->url(),
'avatar' => $profile->avatarUrl(),
'website' => $profile->website,
'local' => (bool) $local,
'is_admin' => (bool) $is_admin,
];
}

View File

@ -36,6 +36,14 @@ class Config {
'site' => [
'domain' => config('pixelfed.domain.app'),
'url' => config('app.url')
],
'username' => [
'remote' => [
'formats' => config('instance.username.remote.formats'),
'format' => config('instance.username.remote.format'),
'custom' => config('instance.username.remote.custom')
]
]
];
});

View File

@ -5,7 +5,7 @@
"license": "AGPL-3.0-only",
"type": "project",
"require": {
"php": "^7.1.3",
"php": "^7.2",
"ext-bcmath": "*",
"ext-ctype": "*",
"ext-curl": "*",
@ -19,7 +19,8 @@
"fideloper/proxy": "^4.0",
"intervention/image": "^2.4",
"jenssegers/agent": "^2.6",
"laravel/framework": "5.8.*",
"laravel/framework": "^6.0",
"laravel/helpers": "^1.1",
"laravel/horizon": "^3.3",
"laravel/passport": "^7.0",
"laravel/tinker": "^1.0",
@ -27,10 +28,9 @@
"league/flysystem-cached-adapter": "~1.0",
"league/iso3166": "^2.1",
"moontoast/math": "^1.1",
"pbmedia/laravel-ffmpeg": "4.0.0",
"pbmedia/laravel-ffmpeg": "5.0.*",
"phpseclib/phpseclib": "~2.0",
"pixelfed/bacon-qr-code": "^3.0",
"pixelfed/dotenv-editor": "^2.0",
"pixelfed/fractal": "^0.18.0",
"pixelfed/google2fa": "^4.0",
"pixelfed/laravel-snowflake": "^2.0",
@ -38,16 +38,16 @@
"predis/predis": "^1.1",
"spatie/laravel-backup": "^6.0.0",
"spatie/laravel-image-optimizer": "^1.1",
"stevebauman/purify": "2.0.*"
"stevebauman/purify": "3.0.*"
},
"require-dev": {
"barryvdh/laravel-debugbar": "dev-master",
"filp/whoops": "^2.0",
"facade/ignition": "^1.4",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^2.0",
"nunomaduro/collision": "^3.0",
"nunomaduro/phpinsights": "^1.7",
"phpunit/phpunit": "^7.5"
"phpunit/phpunit": "^8.0"
},
"autoload": {
"classmap": [

1176
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -39,5 +39,11 @@ return [
'body' => env('PAGE_503_BODY', 'Our service is in maintenance mode, please try again later.')
]
],
'username' => [
'remote' => [
'formats' => ['@', 'from', 'custom'],
'format' => in_array(env('USERNAME_REMOTE_FORMAT', '@'), ['@','from','custom']) ? env('USERNAME_REMOTE_FORMAT', '@') : '@',
'custom' => env('USERNAME_REMOTE_CUSTOM_TEXT', null)
]
],
];

View File

@ -8,7 +8,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends git gosu \
optipng pngquant jpegoptim gifsicle libpq-dev libsqlite3-dev locales zip unzip libzip-dev libcurl4-openssl-dev \
libfreetype6 libicu-dev libjpeg62-turbo libpng16-16 libxpm4 libwebp6 libmagickwand-6.q16-6 \
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev \
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev mariadb-client\
&& sed -i '/en_US/s/^#//g' /etc/locale.gen \
&& locale-gen && update-locale \
&& docker-php-source extract \

View File

@ -8,7 +8,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends git gosu \
optipng pngquant jpegoptim gifsicle libpq-dev libsqlite3-dev locales zip unzip libzip-dev libcurl4-openssl-dev \
libfreetype6 libicu-dev libjpeg62-turbo libpng16-16 libxpm4 libwebp6 libmagickwand-6.q16-6 \
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev \
libfreetype6-dev libjpeg62-turbo-dev libpng-dev libxpm-dev libwebp-dev libmagickwand-dev mariadb-client\
&& sed -i '/en_US/s/^#//g' /etc/locale.gen \
&& locale-gen && update-locale \
&& docker-php-source extract \

View File

@ -30,6 +30,7 @@ server {
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
try_files $fastcgi_script_name =404;
fastcgi_pass unix:/run/php-fpm/php-fpm.sock; # make sure this is correct
fastcgi_index index.php;
include fastcgi_params;

View File

@ -14,7 +14,10 @@ services:
app:
# Comment to use dockerhub image
build: .
build:
context: .
dockerfile: contrib/docker/Dockerfile.apache
#dockerfile: contrib/docker/Dockerfile.fpm
image: pixelfed
restart: unless-stopped
## If you have a traefik running, uncomment this to expose Pixelfed
@ -36,7 +39,10 @@ services:
worker: # Comment this whole block if HORIZON_EMBED is true.
# Comment to use dockerhub image
build: .
build:
context: .
dockerfile: contrib/docker/Dockerfile.apache
#dockerfile: contrib/docker/Dockerfile.fpm
image: pixelfed
restart: unless-stopped
env_file:
@ -54,6 +60,7 @@ services:
restart: unless-stopped
networks:
- internal
command: --default-authentication-plugin=mysql_native_password
environment:
- MYSQL_DATABASE=pixelfed
- MYSQL_USER=${DB_USERNAME}

5
package-lock.json generated
View File

@ -10484,6 +10484,11 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
},
"zuck.js": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/zuck.js/-/zuck.js-1.5.4.tgz",
"integrity": "sha512-vCNaP+mLHzslUJrIj3FakFfno9wKWJatlTKYCW7EjxN4xkodfEIcm5QrE+J9UdPSTn9TTaXrDRgaJZeG3Er7HA=="
}
}
}

View File

@ -8,8 +8,7 @@
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"postinstall": "opencollective-postinstall"
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"axios": "^0.18.1",
@ -28,21 +27,15 @@
"dependencies": {
"@trevoreyre/autocomplete-vue": "^2.0.2",
"bootstrap-vue": "^2.0.0-rc.26",
"emoji-mart-vue": "^2.6.6",
"filesize": "^3.6.1",
"howler": "^2.1.2",
"infinite-scroll": "^3.0.6",
"laravel-echo": "^1.5.4",
"laravel-mix": "^4.1.2",
"node-sass": "^4.12.0",
"opencollective": "^1.0.3",
"opencollective-postinstall": "^2.0.2",
"plyr": "^3.5.6",
"promise-polyfill": "8.1.0",
"pusher-js": "^4.4.0",
"quill": "^1.3.7",
"readmore-js": "^2.2.1",
"socket.io-client": "^2.2.0",
"sweetalert": "^2.1.2",
"twitter-text": "^2.0.5",
"vue-carousel": "^0.18.0",
@ -50,7 +43,8 @@
"vue-cropperjs": "^4.0.0",
"vue-infinite-loading": "^2.4.4",
"vue-loading-overlay": "^3.2.0",
"vue-timeago": "^5.1.2"
"vue-timeago": "^5.1.2",
"zuck.js": "^1.5.4"
},
"collective": {
"type": "opencollective",

4
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/status.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,13 +3,13 @@
"/js/vendor.js": "/js/vendor.js?id=fac92a458473b287c543",
"/js/ace.js": "/js/ace.js?id=585114d8896dc0c24020",
"/js/activity.js": "/js/activity.js?id=713d9542e71e87fb88c0",
"/js/app.js": "/js/app.js?id=16017d5f3472ac2d6ee3",
"/css/app.css": "/css/app.css?id=56379e7c98ba2945e829",
"/css/appdark.css": "/css/appdark.css?id=83064c1642bdd3457156",
"/css/landing.css": "/css/landing.css?id=221a8ef5424fab0875d9",
"/js/app.js": "/js/app.js?id=e247f50c24aaed688cc9",
"/css/app.css": "/css/app.css?id=67a82ced484bdb354748",
"/css/appdark.css": "/css/appdark.css?id=b0fdec1caa1ce13301c6",
"/css/landing.css": "/css/landing.css?id=66d18d3f53fa9d41033c",
"/css/quill.css": "/css/quill.css?id=711b2150d518816d6112",
"/js/collectioncompose.js": "/js/collectioncompose.js?id=b27e524d161917a9e9e1",
"/js/collections.js": "/js/collections.js?id=5e47d9f51d8eaddc4d24",
"/js/collections.js": "/js/collections.js?id=dcc75eec0b3e0736e5fe",
"/js/components.js": "/js/components.js?id=be8c9e1c6c52db778f29",
"/js/compose.js": "/js/compose.js?id=2d3e96bd3197d49cfe88",
"/js/compose-classic.js": "/js/compose-classic.js?id=e7483681a575c190a43b",
@ -18,10 +18,10 @@
"/js/hashtag.js": "/js/hashtag.js?id=3fe97ed3f975f0e0baa5",
"/js/loops.js": "/js/loops.js?id=9c31302552d789d5f35b",
"/js/mode-dot.js": "/js/mode-dot.js?id=993d7fee684361edddbc",
"/js/profile.js": "/js/profile.js?id=749e6e867d987d8d4522",
"/js/profile.js": "/js/profile.js?id=97bec372a25ffd2e909e",
"/js/quill.js": "/js/quill.js?id=37962cd45a252d2f13c9",
"/js/search.js": "/js/search.js?id=f312959df65e86a307a3",
"/js/status.js": "/js/status.js?id=90bc60c7fd41b67a38db",
"/js/status.js": "/js/status.js?id=dd20c64c2a1d7f3c6bce",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=700e5dc735365e184e41",
"/js/timeline.js": "/js/timeline.js?id=8347abbb95fcae63ce63"
"/js/timeline.js": "/js/timeline.js?id=e45ea0de04ac33768c74"
}

View File

@ -20,19 +20,18 @@ window.App.boot = function() {
new Vue({ el: '#content'});
}
window.App.util = {
time: (function() {
return new Date;
}),
version: (function() {
return 1;
}),
version: 1,
format: {
count: (function(count = 0) {
count: (function(count = 0, locale = 'en-GB', notation = 'compact') {
if(count < 1) {
return 0;
}
return new Intl.NumberFormat('en-GB', { notation: "compact" , compactDisplay: "short" }).format(count);
return new Intl.NumberFormat(locale, { notation: notation , compactDisplay: "short" }).format(count);
})
},
filters: [
@ -78,5 +77,6 @@ window.App.util = {
['Willow','filter-willow'],
['X-Pro II','filter-xpro-ii']
],
emoji: ['😂','💯','❤️','🙌','👏','👌','😍','😯','😢','😅','😁','🙂','😎','😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'],
emoji: ['😂','💯','❤️','🙌','👏','👌','😍','😯','😢','😅','😁','🙂','😎','😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'
],
};

View File

@ -15,7 +15,14 @@
</p>
<p v-if="owner == true" class="pt-3 text-center">
<span>
<button class="btn btn-outline-light btn-sm" @click.prevent="addToCollection">Add Photo</button>
<button class="btn btn-outline-light btn-sm" @click.prevent="addToCollection">
<span v-if="loadingPostList == false">Add Photo</span>
<span v-else class="px-4">
<div class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">Loading...</span>
</div>
</span>
</button>
&nbsp; &nbsp;
<button class="btn btn-outline-light btn-sm" @click.prevent="editCollection">Edit</button>
&nbsp; &nbsp;
@ -62,7 +69,20 @@
<button type="button" class="btn btn-primary btn-sm py-1 font-weight-bold px-3 float-right" @click.prevent="updateCollection">Save</button>
</form>
</b-modal>
<b-modal ref="addPhotoModal" id="add-photo-modal" hide-footer centered title="Add Photo" body-class="">
<b-modal ref="addPhotoModal" id="add-photo-modal" hide-footer centered title="Add Photo" body-class="m-3">
<div class="form-group">
<label for="title" class="font-weight-bold text-muted">Add Recent Post</label>
<div class="row m-1" v-if="postsList.length > 0">
<div v-for="(p, index) in postsList" :key="'postList-'+index" class="col-4 p-1 cursor-pointer">
<div class="square">
<div class="square-content" v-bind:style="'background-image: url(' + p.media_attachments[0].url + ');'"></div>
</div>
</div>
<div class="col-12">
<hr>
</div>
</div>
</div>
<form>
<div class="form-group">
<label for="title" class="font-weight-bold text-muted">Add Post by URL</label>
@ -105,12 +125,15 @@ export default {
return {
loaded: false,
posts: [],
ids: [],
currentUser: false,
owner: false,
title: this.collectionTitle,
description: this.collectionDescription,
visibility: this.collectionVisibility,
photoId: ''
photoId: '',
postsList: [],
loadingPostList: false
}
},
@ -135,6 +158,9 @@ export default {
axios.get('/api/local/collection/items/' + this.collectionId)
.then(res => {
this.posts = res.data;
this.ids = this.posts.map(p => {
return p.id;
});
this.loaded = true;
});
},
@ -149,11 +175,34 @@ export default {
},
addToCollection() {
this.$refs.addPhotoModal.show();
let self = this;
this.loadingPostList = true;
if(this.postsList.length == 0) {
axios.get('/api/pixelfed/v1/accounts/'+this.profileId+'/statuses', {
params: {
min_id: 1,
limit: 13
}
})
.then(res => {
self.postsList = res.data.filter(l => {
return self.ids.indexOf(l.id) == -1;
}).splice(0,9);
self.loadingPostList = false;
self.$refs.addPhotoModal.show();
}).catch(err => {
self.loadingPostList = false;
swal('An Error Occured', 'We cannot process your request at this time, please try again later.', 'error');
})
} else {
this.$refs.addPhotoModal.show();
this.loadingPostList = false;
}
},
pushId() {
let max = 18;
let self = this;
if(this.posts.length >= max) {
swal('Error', 'You can only add ' + max + ' posts per collection', 'error');
return;
@ -174,7 +223,7 @@ export default {
collection_id: this.collectionId,
post_id: split[5]
}).then(res => {
location.reload();
self.ids.push(...split[5]);
}).catch(err => {
swal('Invalid URL', 'The post you entered was invalid', 'error');
this.photoId = '';

View File

@ -164,7 +164,7 @@
<div class="profile-timeline mt-md-4">
<div class="row" v-if="mode == 'grid'">
<div class="col-4 p-1 p-md-3" v-for="(s, index) in timeline">
<a class="card info-overlay card-md-border-0" :href="s.url">
<a class="card info-overlay card-md-border-0" :href="statusUrl(s)">
<div :class="[s.sensitive ? 'square' : 'square ' + s.media_attachments[0].filter_class]">
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
@ -329,7 +329,7 @@
:gutter="{default: '5px'}"
>
<div class="p-1" v-for="(s, index) in timeline">
<a :class="[s.sensitive ? 'card info-overlay card-md-border-0' : s.media_attachments[0].filter_class + ' card info-overlay card-md-border-0']" :href="s.url">
<a :class="[s.sensitive ? 'card info-overlay card-md-border-0' : s.media_attachments[0].filter_class + ' card info-overlay card-md-border-0']" :href="statusUrl(s)">
<img :src="previewUrl(s)" class="img-fluid w-100">
</a>
</div>
@ -1080,6 +1080,22 @@
formatCount(count) {
return App.util.format.count(count);
},
statusUrl(status) {
if(status.local == true) {
return status.url;
}
return '/i/web/post/_/' + status.account.id + '/' + status.id;
},
profileUrl(status) {
if(status.local == true) {
return status.account.url;
}
return '/i/web/profile/_/' + status.account.id;
}
}
}

View File

@ -2,6 +2,7 @@
<div class="container" style="">
<div class="row">
<div :class="[modes.distractionFree ? 'col-md-8 col-lg-8 offset-md-2 px-0 my-sm-3 timeline order-2 order-md-1':'col-md-8 col-lg-8 px-0 my-sm-3 timeline order-2 order-md-1']">
<div class="d-none" data-id="StoryTimelineComponent"></div>
<div style="padding-top:10px;">
<div v-if="loading" class="text-center">
<div class="spinner-border" role="status">
@ -69,11 +70,11 @@
<div class="card mb-sm-4 status-card card-md-rounded-0 shadow-none border">
<div v-if="!modes.distractionFree" class="card-header d-inline-flex align-items-center bg-white">
<img v-bind:src="status.account.avatar" width="32px" height="32px" style="border-radius: 32px;">
<img v-bind:src="status.account.avatar" width="32px" height="32px" class="cursor-pointer" style="border-radius: 32px;" @click="profileUrl(status)">
<div class="pl-2">
<!-- <a class="d-block username font-weight-bold text-dark" v-bind:href="status.account.url" style="line-height:0.5;"> -->
<a class="username font-weight-bold text-dark text-decoration-none" v-bind:href="status.account.url">
{{status.account.username}}
<a class="username font-weight-bold text-dark text-decoration-none" v-bind:href="profileUrl(status)" v-html="statusCardUsernameFormat(status)">
Loading...
</a>
<span v-if="status.account.is_admin" class="fa-stack" title="Admin Account" data-toggle="tooltip" style="height:1em; line-height:1em; max-width:19px;">
<i class="fas fa-certificate text-danger fa-stack-1x"></i>
@ -158,7 +159,7 @@
<h3 v-bind:class="[status.favourited ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn text-lighter cursor-pointer']" title="Like" v-on:click="likeStatus(status, $event)"></h3>
<h3 v-if="!status.comments_disabled" class="far fa-comment text-lighter pr-3 m-0 cursor-pointer" title="Comment" v-on:click="commentFocus(status, $event)"></h3>
<h3 v-if="status.visibility == 'public'" v-bind:class="[status.reblogged ? 'fas fa-retweet pr-3 m-0 text-primary cursor-pointer' : 'fas fa-retweet pr-3 m-0 text-lighter share-btn cursor-pointer']" title="Share" v-on:click="shareStatus(status, $event)"></h3>
<span class="float-right">
<span v-if="status.pf_type == 'photo'" class="float-right">
<h3 class="fas fa-expand pr-3 m-0 cursor-pointer text-lighter" v-on:click="lightbox(status)"></h3>
</span>
</div>
@ -169,15 +170,15 @@
<div class="caption">
<p class="mb-2 read-more" style="overflow: hidden;">
<span class="username font-weight-bold">
<bdi><a class="text-dark" :href="status.account.url">{{status.account.username}}</a></bdi>
<bdi><a class="text-dark" :href="profileUrl(status)">{{status.account.username}}</a></bdi>
</span>
<span v-html="status.content"></span>
<span class="status-content" v-html="status.content"></span>
</p>
</div>
<div class="comments" v-if="status.id == replyId && !status.comments_disabled">
<p class="mb-0 d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;" v-for="(reply, index) in replies">
<span>
<a class="text-dark font-weight-bold mr-1" :href="reply.account.url">{{reply.account.username}}</a>
<a class="text-dark font-weight-bold mr-1" :href="profileUrl(reply)">{{reply.account.username}}</a>
<span v-html="reply.content"></span>
</span>
<span class="mb-0" style="min-width:38px">
@ -191,7 +192,7 @@
</div>
<div class="timestamp mt-2">
<p class="small text-uppercase mb-0">
<a :href="status.url" class="text-muted">
<a :href="statusUrl(status)" class="text-muted">
<timeago :datetime="status.created_at" :auto-update="60" :converter-options="{includeSeconds:true}" :title="timestampFormat(status.created_at)" v-b-tooltip.hover.bottom></timeago>
</a>
<a v-if="modes.distractionFree" class="float-right" :href="status.url">
@ -467,7 +468,6 @@
profile: {},
min_id: 0,
max_id: 0,
stories: {},
suggestions: {},
loading: true,
replies: [],
@ -579,7 +579,7 @@
axios.get(apiUrl, {
params: {
max_id: this.max_id,
limit: 5
limit: 3
}
}).then(res => {
let data = res.data;
@ -596,6 +596,7 @@
if(this.hashtagPosts.length == 0) {
this.fetchHashtagPosts();
}
// this.fetchStories();
}).catch(err => {
swal(
'Oops, something went wrong',
@ -1159,14 +1160,14 @@
if(tags.length == 0) {
return;
}
let hashtag = tags[0];
let hashtag = tags[Math.floor(Math.random(), tags.length)];
this.hashtagPostsName = hashtag;
axios.get('/api/v2/discover/tag', {
params: {
hashtag: hashtag
}
}).then(res => {
if(res.data.tags.length) {
if(res.data.tags.length > 3) {
this.showHashtagPosts = true;
this.hashtagPosts = res.data.tags.splice(0,3);
}
@ -1210,7 +1211,7 @@
ctxMenuGoToPost() {
let status = this.ctxMenuStatus;
window.location.href = status.url;
window.location.href = this.statusUrl(status);
this.closeCtxMenu();
return;
},
@ -1302,8 +1303,57 @@
formatCount(count) {
return App.util.format.count(count);
}
},
statusUrl(status) {
return status.url;
// if(status.local == true) {
// return status.url;
// }
// return '/i/web/post/_/' + status.account.id + '/' + status.id;
},
profileUrl(status) {
return status.account.url;
// if(status.local == true) {
// return status.account.url;
// }
// return '/i/web/profile/_/' + status.account.id;
},
statusCardUsernameFormat(status) {
if(status.account.local == true) {
return status.account.username;
}
let fmt = window.App.config.username.remote.format;
let txt = window.App.config.username.remote.custom;
let usr = status.account.username;
let dom = document.createElement('a');
dom.href = status.account.url;
dom = dom.hostname;
switch(fmt) {
case '@':
return usr + '<span class="text-lighter font-weight-bold">@' + dom + '</span>';
break;
case 'from':
return usr + '<span class="text-lighter font-weight-bold"> <span class="font-weight-normal">from</span> ' + dom + '</span>';
break;
case 'custom':
return usr + '<span class="text-lighter font-weight-bold"> ' + txt + ' ' + dom + '</span>';
break;
default:
return usr + '<span class="text-lighter font-weight-bold">@' + dom + '</span>';
break;
}
},
}
}
</script>
</script>

View File

@ -5,22 +5,6 @@
<p class="mb-0 lead font-weight-bold">{{ status.spoiler_text ? status.spoiler_text : 'CW / NSFW / Hidden Media'}}</p>
<p class="font-weight-light">(click to show)</p>
</summary>
<!-- <b-carousel :id="status.id + '-carousel'"
v-model="cursor"
style="text-shadow: 1px 1px 2px #333;min-height: 330px;display: flex;align-items: center;"
controls
background="#ffffff"
:interval="0"
>
<b-carousel-slide v-for="(img, index) in status.media_attachments" :key="img.id">
<div slot="img" class="d-block mx-auto text-center" style="max-height: 600px;" :title="img.description">
<img :class="img.filter_class + ' img-fluid'" style="max-height: 600px;" :src="img.url" :alt="img.description" loading="lazy">
</div>
</b-carousel-slide>
<span class="badge badge-dark box-shadow" style="position: absolute;top:10px;right:10px;">
{{cursor + 1}} / {{status.media_attachments.length}}
</span>
</b-carousel> -->
<carousel ref="carousel" :centerMode="true" :loop="false" :per-page="1" :paginationPosition="'bottom-overlay'" paginationActiveColor="#3897f0" paginationColor="#dbdbdb">
<slide v-for="(img, index) in status.media_attachments" :key="'px-carousel-'+img.id + '-' + index" class="w-100 h-100 d-block mx-auto text-center" :title="img.description">
<img :class="img.filter_class + ' img-fluid'" :src="img.url" :alt="img.description">
@ -29,22 +13,6 @@
</details>
</div>
<div v-else class="w-100 h-100 p-0">
<!-- <b-carousel :id="status.id + '-carousel'"
v-model="cursor"
style="text-shadow: 1px 1px 2px #333;min-height: 330px;display: flex;align-items: center;"
controls
background="#ffffff"
:interval="0"
>
<b-carousel-slide v-for="(img, index) in status.media_attachments" :key="img.id" :title="img.description">
<div slot="img" class="d-block mx-auto text-center" style="max-height: 600px;">
<img :class="img.filter_class + ' img-fluid'" style="max-height: 600px;" :src="img.url" loading="lazy" :alt="img.description">
</div>
</b-carousel-slide>
<span class="badge badge-dark box-shadow" style="position: absolute;top:10px;right:10px;">
{{cursor + 1}} / {{status.media_attachments.length}}
</span>
</b-carousel> -->
<carousel ref="carousel" :centerMode="true" :loop="false" :per-page="1" :paginationPosition="'bottom-overlay'" paginationActiveColor="#3897f0" paginationColor="#dbdbdb" class="p-0 m-0">
<slide v-for="(img, index) in status.media_attachments" :key="'px-carousel-'+img.id + '-' + index" class="" style="background: #000; display: flex;align-items: center;" :title="img.description">
<img :class="img.filter_class + ' img-fluid w-100 p-0'" style="" :src="img.url" :alt="img.description">

View File

@ -6,14 +6,14 @@
<p class="font-weight-light">(click to show)</p>
</summary>
<div class="embed-responsive embed-responsive-1by1">
<video class="video" preload="none" controls loop :poster="status.media_attachments[0].preview_url">
<video class="video" preload="none" loop :poster="status.media_attachments[0].preview_url":data-id="status.id" @click="playOrPause($event)">
<source :src="status.media_attachments[0].url" :type="status.media_attachments[0].mime">
</video>
</div>
</details>
</div>
<div v-else class="embed-responsive embed-responsive-16by9">
<video class="video" preload="auto" controls loop :poster="status.media_attachments[0].preview_url">
<video class="video" controls preload="metadata" loop :poster="status.media_attachments[0].preview_url" :data-id="status.id">
<source :src="status.media_attachments[0].url" :type="status.media_attachments[0].mime">
</video>
</div>
@ -22,5 +22,18 @@
<script type="text/javascript">
export default {
props: ['status'],
methods: {
playOrPause(e) {
let el = e.target;
if(el.getAttribute('playing') == 1) {
el.removeAttribute('playing');
el.pause();
} else {
el.setAttribute('playing', 1);
el.play();
}
}
}
}
</script>

View File

@ -21,8 +21,4 @@
@import '~bootstrap-vue/dist/bootstrap-vue.css';
@import '~plyr/dist/plyr.css';
@import '~vue-loading-overlay/dist/vue-loading.css';
@import "moment";

View File

@ -72,10 +72,6 @@ textarea {
@import '~bootstrap-vue/dist/bootstrap-vue.css';
@import '~plyr/dist/plyr.css';
@import '~vue-loading-overlay/dist/vue-loading.css';
@import "moment";
.border {

View File

@ -565,4 +565,8 @@ details summary::-webkit-details-marker {
.VueCarousel-dot:focus,
.VueCarousel-dot--active:focus {
outline: 0px !important;
}
.status-content > p:first-child {
display: inline;
}

View File

@ -14,6 +14,6 @@ return [
*/
'failed' => 'Diese Anmeldeinformationen stimmen nicht mit unseren Daten überein.',
'throttle' => 'Zu viele Login-Versuche. Versuche es in :seconds Sekunden erneut.',
'throttle' => 'Zu viele Anmeldeversuche. Versuche es in :seconds Sekunden erneut.',
];

View File

@ -15,7 +15,7 @@ return [
'timelines' => 'Timelines',
'embed' => 'Einbetten',
'communityGuidelines' => 'Community-Richtlinien',
'communityGuidelines' => 'Gemeinschaftsrichtlinien',
'whatIsTheFediverse' => 'Was ist das Fediversum?',
'controllingVisibility' => 'Sichtbarkeit steuern',
'blockingAccounts' => 'Kontosperrung',

View File

@ -2,11 +2,11 @@
return [
'likedPhoto' => 'gefällt dein Foto.',
'likedPhoto' => 'gefällt dein Beitrag.',
'likedComment' => 'gefällt dein Kommentar.',
'startedFollowingYou' => 'folgt dir nun.',
'commented' => 'hat deinen Post kommentiert.',
'commented' => 'hat deinen Beitrag kommentiert.',
'mentionedYou' => 'hat dich erwähnt.',
'shared' => 'hat deinen Post teilen.',
'shared' => 'hat deinen Beitrag geteilt.',
];

View File

@ -15,8 +15,8 @@ return [
'password' => 'Passwörter müssen mindestens 6 Zeichen lang sein und mit der Bestätigung übereinstimmen.',
'reset' => 'Dein Passwort wurde zurückgesetzt!',
'sent' => 'Wir haben dir eine E-Mail zum Zurücksetzen deines Passworts gesendet!',
'token' => 'Dieser Passwort-Reset-Code ist ungültig.',
'user' => 'Wir konnten keinen Nutzer mit dieser E-Mail-Adresse finden.',
'sent' => 'Wenn deine E-Mail-Adresse in unserer Datenbank existiert, wirst du in ein paar Minuten einen Link zum Zurücksetzen deines Passworts zugesendet bekommen. Bitte prüfe deinen Spam-Ordner, wenn du diese E-Mail nicht bekommst.',
'token' => 'Dieser Code zum Passwort zurücksetzen ist ungültig.',
'user' => 'Wenn deine E-Mail-Adresse in unserer Datenbank existiert, wirst du in ein paar Minuten einen Link zum Zurücksetzen deines Passworts zugesendet bekommen. Bitte prüfe deinen Spam-Ordner, wenn du diese E-Mail nicht bekommst.',
];

View File

@ -1,15 +1,15 @@
<?php
return [
'emptyTimeline' => 'Dieser Benutzer hat noch nichts gepostet!',
'emptyTimeline' => 'Dieser Benutzer hat noch nichts beigetragen!',
'emptyFollowers' => 'Diesem Benutzer folgt noch niemand!',
'emptyFollowing' => 'Dieser Benutzer folgt noch niemanden!',
'emptySaved' => 'Du hast noch keinen Post gespeichert!',
'savedWarning' => 'Nur du kannst sehen was du gespeichert hast',
'privateProfileWarning' => 'Dieser Account ist privat',
'emptySaved' => 'Du hast noch keinen Beitrag gespeichert!',
'savedWarning' => 'Nur du kannst sehen, was du gespeichert hast',
'privateProfileWarning' => 'Dieses Konto ist privat',
'alreadyFollow' => ':username bereits folgen?',
'loginToSeeProfile' => 'um deren Bilder und Videos zu sehen.',
'status.disabled.header' => 'Profil nicht verfügbar',
'status.disabled.body' => 'Entschuldigung, dieses Profil ist im Moment nicht verfügbar. Bitte versuchen Sie es später noch einmal.',
'status.disabled.body' => 'Entschuldigung, dieses Profil ist im Moment nicht verfügbar. Bitte versuche es in Kürze noch einmal.',
];

View File

@ -8,12 +8,12 @@ return [
'fediverse' => 'Fediverse',
'opensource' => 'Open Source',
'terms' => 'Nutzungshinweise',
'privacy' => 'Privacy',
'privacy' => 'Datenschutz',
'l10nWip' => 'Wir arbeiten noch an der Unterstützung weiterer Sprachen',
'currentLocale' => 'Aktuelle Sprache',
'selectLocale' => 'Wähle eine der unterstützten Sprachen aus',
'contact' => 'Kontakt',
'contact-us' => 'Kontaktiere uns',
'places' => 'Plätze',
'places' => 'Orte',
];

View File

@ -1,4 +1,5 @@
<!DOCTYPE html>
@auth
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
@ -8,7 +9,7 @@
<meta name="mobile-web-app-capable" content="yes">
<title>{{ $title ?? config('app.name', 'Laravel') }}</title>
<title>{{ $title ?? config('app.name', 'Pixelfed') }}</title>
<link rel="manifest" href="/manifest.json">
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
@ -36,11 +37,10 @@
<script type="text/javascript">window.App = {config: {!!App\Util\Site\Config::json()!!}};</script>
</head>
<body class="{{Auth::check()?'loggedIn':''}}">
<body class="loggedIn">
@include('layouts.partial.nav')
<main id="content">
@yield('content')
@if(Auth::check())
<div class="modal pr-0" tabindex="-1" role="dialog" id="composeModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
@ -48,7 +48,6 @@
</div>
</div>
</div>
@endif
</main>
@include('layouts.partial.footer')
<script type="text/javascript" src="{{ mix('js/manifest.js') }}"></script>
@ -56,7 +55,6 @@
<script type="text/javascript" src="{{ mix('js/app.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/components.js') }}"></script>
@stack('scripts')
@if(Auth::check())
<div class="d-block d-sm-none mt-5"></div>
<div class="d-block d-sm-none fixed-bottom">
<div class="card card-body rounded-0 py-2 d-flex align-items-middle box-shadow" style="border-top:1px solid #F1F5F8">
@ -79,6 +77,48 @@
</ul>
</div>
</div>
@endif
</body>
</html>
@endauth
@guest
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<title>{{ $title ?? config('app.name', 'Pixelfed') }}</title>
<link rel="manifest" href="/manifest.json">
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
<meta property="og:type" content="article">
<meta property="og:url" content="{{request()->url()}}">
@stack('meta')
<meta name="medium" content="image">
<meta name="theme-color" content="#10c5f8">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="shortcut icon" type="image/png" href="/img/favicon.png?v=2">
<link rel="apple-touch-icon" type="image/png" href="/img/favicon.png?v=2">
<link rel="canonical" href="{{request()->url()}}">
<link href="{{ mix('css/app.css') }}" rel="stylesheet" data-stylesheet="light">
@stack('styles')
</head>
<body>
@include('layouts.partial.nav')
<main id="content">
@yield('content')
</main>
@include('layouts.partial.footer')
<script type="text/javascript" src="{{ mix('js/manifest.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/vendor.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/app.js') }}"></script>
<script type="text/javascript" src="{{ mix('js/components.js') }}"></script>
@stack('scripts')
</body>
</html>
@endguest

View File

@ -33,9 +33,9 @@
<li class="nav-item pl-3 {{request()->is('settings/security*')?'active':''}}">
<a class="nav-link font-weight-light text-muted" href="{{route('settings.security')}}">Security</a>
</li>
<li class="nav-item pl-3 {{request()->is('settings/sponsor*')?'active':''}}">
{{-- <li class="nav-item pl-3 {{request()->is('settings/sponsor*')?'active':''}}">
<a class="nav-link font-weight-light text-muted" href="{{route('settings.sponsor')}}">Sponsor</a>
</li>
</li> --}}
<li class="nav-item">
<hr>
</li>

View File

@ -17,9 +17,12 @@
@push('meta')
<meta property="og:description" content="{{ $status->caption }}">
<meta property="og:image" content="{{$status->mediaUrl()}}">
<meta property="og:image" content="{{$status->thumb()}}">
<link href='{{$status->url()}}' rel='alternate' type='application/activity+json'>
<meta name="twitter:card" content="summary_large_image">
@if($status->viewType() == "video" || $status->viewType() == "video:album")
<meta property="og:video" content="{{$status->mediaUrl()}}">
@endif
@endpush
@push('scripts')

View File

@ -60,7 +60,7 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
Route::post('statuses/{id}/unreblog', 'Api\ApiV1Controller@statusUnshare')->middleware($middleware);
Route::delete('statuses/{id}', 'Api\ApiV1Controller@statusDelete')->middleware($middleware);
Route::get('statuses/{id}', 'Api\ApiV1Controller@statusById')->middleware($middleware);
Route::post('statuses', 'Api\ApiV1Controller@statusCreate')->middleware($middleware);
Route::post('statuses', 'Api\ApiV1Controller@statusCreate')->middleware($middleware)->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440');
Route::get('timelines/home', 'Api\ApiV1Controller@timelineHome')->middleware($middleware);

View File

@ -149,7 +149,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('exp/rec', 'ApiController@userRecommendations');
Route::post('discover/tag/subscribe', 'HashtagFollowController@store')->middleware('throttle:maxHashtagFollowsPerHour,60')->middleware('throttle:maxHashtagFollowsPerDay,1440');;
Route::get('discover/tag/list', 'HashtagFollowController@getTags');
Route::get('profile/sponsor/{id}', 'ProfileSponsorController@get');
// Route::get('profile/sponsor/{id}', 'ProfileSponsorController@get');
Route::get('bookmarks', 'InternalApiController@bookmarks');
Route::get('collection/items/{id}', 'CollectionController@getItems');
Route::post('collection/item', 'CollectionController@storeId');
@ -318,8 +318,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('invites/create', 'UserInviteController@create')->name('settings.invites.create');
Route::post('invites/create', 'UserInviteController@store');
Route::get('invites', 'UserInviteController@show')->name('settings.invites');
Route::get('sponsor', 'SettingsController@sponsor')->name('settings.sponsor');
Route::post('sponsor', 'SettingsController@sponsorStore');
// Route::get('sponsor', 'SettingsController@sponsor')->name('settings.sponsor');
// Route::post('sponsor', 'SettingsController@sponsorStore');
});
Route::group(['prefix' => 'site'], function () {

View File

@ -5,6 +5,7 @@ namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use App\User;
class LoginTest extends TestCase
{
@ -16,18 +17,4 @@ class LoginTest extends TestCase
$response->assertSee('Forgot Password');
}
/** @test */
public function view_register_page()
{
if(true == config('pixelfed.open_registration')) {
$response = $this->get('register');
$response->assertSee('Register a new account');
} else {
$response = $this->get('register');
$response->assertSee('Registration is closed');
}
}
}