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 1061 additions and 584 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",

BIN
public/css/app.css vendored

Binary file not shown.

BIN
public/css/appdark.css vendored

Binary file not shown.

BIN
public/css/landing.css vendored

Binary file not shown.

BIN
public/js/app.js vendored

Binary file not shown.

Binary file not shown.

BIN
public/js/profile.js vendored

Binary file not shown.

BIN
public/js/status.js vendored

Binary file not shown.

BIN
public/js/timeline.js vendored

Binary file not shown.

Binary file not shown.

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');
}
}
}