parent
232ba11534
commit
de80db1b96
7 changed files with 90 additions and 5 deletions
10
ansible/templates/nginx.conf
vendored
10
ansible/templates/nginx.conf
vendored
|
@ -46,6 +46,9 @@ server {
|
||||||
add_header X-Frame-Options "DENY";
|
add_header X-Frame-Options "DENY";
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
|
||||||
|
# Upload limit for pictshare
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
rewrite (\/(user|u\/|inbox|post|community|c\/|create_post|create_community|login|search|setup|sponsors|communities|modlog|home)+) /static/index.html break;
|
rewrite (\/(user|u\/|inbox|post|community|c\/|create_post|create_community|login|search|setup|sponsors|communities|modlog|home)+) /static/index.html break;
|
||||||
proxy_pass http://0.0.0.0:8536;
|
proxy_pass http://0.0.0.0:8536;
|
||||||
|
@ -58,4 +61,11 @@ server {
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /pictshare/ {
|
||||||
|
proxy_pass http://0.0.0.0:8537/;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
7
docker/dev/docker-compose.yml
vendored
7
docker/dev/docker-compose.yml
vendored
|
@ -22,5 +22,12 @@ services:
|
||||||
- HOSTNAME=${DOMAIN}
|
- HOSTNAME=${DOMAIN}
|
||||||
depends_on:
|
depends_on:
|
||||||
- lemmy_db
|
- lemmy_db
|
||||||
|
lemmy_pictshare:
|
||||||
|
image: hascheksolutions/pictshare:latest
|
||||||
|
ports:
|
||||||
|
- "8537:80"
|
||||||
|
volumes:
|
||||||
|
- lemmy_pictshare:/usr/share/nginx/html/data
|
||||||
volumes:
|
volumes:
|
||||||
lemmy_db:
|
lemmy_db:
|
||||||
|
lemmy_pictshare:
|
||||||
|
|
9
docker/prod/docker-compose.yml
vendored
9
docker/prod/docker-compose.yml
vendored
|
@ -20,5 +20,12 @@ services:
|
||||||
- HOSTNAME=${DOMAIN}
|
- HOSTNAME=${DOMAIN}
|
||||||
depends_on:
|
depends_on:
|
||||||
- lemmy_db
|
- lemmy_db
|
||||||
|
lemmy_pictshare:
|
||||||
|
image: hascheksolutions/pictshare:latest
|
||||||
|
ports:
|
||||||
|
- "8537:80"
|
||||||
|
volumes:
|
||||||
|
- lemmy_pictshare:/usr/share/nginx/html/data
|
||||||
volumes:
|
volumes:
|
||||||
lemmy_db:
|
lemmy_db:
|
||||||
|
lemmy_pictshare:
|
||||||
|
|
29
ui/src/components/comment-form.tsx
vendored
29
ui/src/components/comment-form.tsx
vendored
|
@ -136,7 +136,10 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
}
|
}
|
||||||
{this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}><T i18nKey="cancel">#</T></button>}
|
{this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}><T i18nKey="cancel">#</T></button>}
|
||||||
<a href={markdownHelpUrl} target="_blank" class="d-inline-block float-right text-muted small font-weight-bold"><T i18nKey="formatting_help">#</T></a>
|
<a href={markdownHelpUrl} target="_blank" class="d-inline-block float-right text-muted small font-weight-bold"><T i18nKey="formatting_help">#</T></a>
|
||||||
<a href={imageUploadUrl} target="_blank" class="d-inline-block mr-2 float-right text-muted small font-weight-bold"><T i18nKey="upload_image">#</T></a>
|
<form class="d-inline-block mr-2 float-right text-muted small font-weight-bold">
|
||||||
|
<label htmlFor={`file-upload-${this.id}`} class="pointer"><T i18nKey="upload_image">#</T></label>
|
||||||
|
<input id={`file-upload-${this.id}`} type="file" name="file" class="d-none" onChange={linkEvent(this, this.handleImageUpload)} />
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -154,8 +157,8 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
|
|
||||||
i.state.previewMode = false;
|
i.state.previewMode = false;
|
||||||
i.state.commentForm.content = undefined;
|
i.state.commentForm.content = undefined;
|
||||||
i.setState(i.state);
|
|
||||||
event.target.reset();
|
event.target.reset();
|
||||||
|
i.setState(i.state);
|
||||||
if (i.props.node) {
|
if (i.props.node) {
|
||||||
i.props.onReplyCancel();
|
i.props.onReplyCancel();
|
||||||
}
|
}
|
||||||
|
@ -178,6 +181,28 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
i.props.onReplyCancel();
|
i.props.onReplyCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleImageUpload(i: CommentForm, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
let file = event.target.files[0];
|
||||||
|
const imageUploadUrl = `/pictshare/api/upload.php`;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
fetch(imageUploadUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
let url = `${window.location.origin}/pictshare/${res.url}`;
|
||||||
|
let markdown = (res.filetype == 'mp4') ? `[vid](${url}/raw)` : `![](${url})`;
|
||||||
|
let content = i.state.commentForm.content;
|
||||||
|
content = (content) ? `${content} ${markdown}` : markdown;
|
||||||
|
i.state.commentForm.content = content;
|
||||||
|
i.setState(i.state);
|
||||||
|
})
|
||||||
|
.catch((error) => alert(error));
|
||||||
|
}
|
||||||
|
|
||||||
userSearch(text: string, cb: any) {
|
userSearch(text: string, cb: any) {
|
||||||
if (text) {
|
if (text) {
|
||||||
let form: SearchForm = {
|
let form: SearchForm = {
|
||||||
|
|
28
ui/src/components/post-form.tsx
vendored
28
ui/src/components/post-form.tsx
vendored
|
@ -109,7 +109,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
{this.state.suggestedTitle &&
|
{this.state.suggestedTitle &&
|
||||||
<div class="mt-1 text-muted small font-weight-bold pointer" onClick={linkEvent(this, this.copySuggestedTitle)}><T i18nKey="copy_suggested_title" interpolation={{title: this.state.suggestedTitle}}>#</T></div>
|
<div class="mt-1 text-muted small font-weight-bold pointer" onClick={linkEvent(this, this.copySuggestedTitle)}><T i18nKey="copy_suggested_title" interpolation={{title: this.state.suggestedTitle}}>#</T></div>
|
||||||
}
|
}
|
||||||
<a href={imageUploadUrl} target="_blank" class="d-inline-block mr-2 float-right text-muted small font-weight-bold"><T i18nKey="upload_image">#</T></a>
|
<form>
|
||||||
|
<label htmlFor="file-upload" class="pointer d-inline-block mr-2 float-right text-muted small font-weight-bold"><T i18nKey="upload_image">#</T></label>
|
||||||
|
<input id="file-upload" type="file" name="file" class="d-none" onChange={linkEvent(this, this.handleImageUpload)} />
|
||||||
|
</form>
|
||||||
{this.state.crossPosts.length > 0 &&
|
{this.state.crossPosts.length > 0 &&
|
||||||
<>
|
<>
|
||||||
<div class="my-1 text-muted small font-weight-bold"><T i18nKey="cross_posts">#</T></div>
|
<div class="my-1 text-muted small font-weight-bold"><T i18nKey="cross_posts">#</T></div>
|
||||||
|
@ -266,6 +269,29 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleImageUpload(i: PostForm, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
let file = event.target.files[0];
|
||||||
|
const imageUploadUrl = `/pictshare/api/upload.php`;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
fetch(imageUploadUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
let url = `${window.location.origin}/pictshare/${res.url}`;
|
||||||
|
if (res.filetype == 'mp4') {
|
||||||
|
url += '/raw';
|
||||||
|
}
|
||||||
|
|
||||||
|
i.state.postForm.url = url;
|
||||||
|
i.setState(i.state);
|
||||||
|
})
|
||||||
|
.catch((error) => alert(error));
|
||||||
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: any) {
|
||||||
let op: UserOperation = msgOp(msg);
|
let op: UserOperation = msgOp(msg);
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
|
|
7
ui/src/components/post-listing.tsx
vendored
7
ui/src/components/post-listing.tsx
vendored
|
@ -4,7 +4,7 @@ import { WebSocketService, UserService } from '../services';
|
||||||
import { Post, CreatePostLikeForm, PostForm as PostFormI, SavePostForm, CommunityUser, UserView, BanType, BanFromCommunityForm, BanUserForm, AddModToCommunityForm, AddAdminForm, TransferSiteForm, TransferCommunityForm } from '../interfaces';
|
import { Post, CreatePostLikeForm, PostForm as PostFormI, SavePostForm, CommunityUser, UserView, BanType, BanFromCommunityForm, BanUserForm, AddModToCommunityForm, AddAdminForm, TransferSiteForm, TransferCommunityForm } from '../interfaces';
|
||||||
import { MomentTime } from './moment-time';
|
import { MomentTime } from './moment-time';
|
||||||
import { PostForm } from './post-form';
|
import { PostForm } from './post-form';
|
||||||
import { mdToHtml, canMod, isMod, isImage, getUnixTime } from '../utils';
|
import { mdToHtml, canMod, isMod, isImage, isVideo, getUnixTime } from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
@ -86,6 +86,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
{post.url && isImage(post.url) &&
|
{post.url && isImage(post.url) &&
|
||||||
<span title={i18n.t('expand_here')} class="pointer" onClick={linkEvent(this, this.handleImageExpandClick)}><img class="mx-2 mt-1 float-left img-fluid thumbnail rounded" src={post.url} /></span>
|
<span title={i18n.t('expand_here')} class="pointer" onClick={linkEvent(this, this.handleImageExpandClick)}><img class="mx-2 mt-1 float-left img-fluid thumbnail rounded" src={post.url} /></span>
|
||||||
}
|
}
|
||||||
|
{post.url && isVideo(post.url) &&
|
||||||
|
<video controls autoPlay muted loop class="mx-2 mt-1 float-left img-fluid thumbnail rounded">
|
||||||
|
<source src={post.url} type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
}
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
<div className="post-title">
|
<div className="post-title">
|
||||||
<h5 className="mb-0 d-inline">
|
<h5 className="mb-0 d-inline">
|
||||||
|
|
5
ui/src/utils.ts
vendored
5
ui/src/utils.ts
vendored
|
@ -104,11 +104,16 @@ export function isMod(modIds: Array<number>, creator_id: number): boolean {
|
||||||
|
|
||||||
|
|
||||||
var imageRegex = new RegExp(`(http)?s?:?(\/\/[^"']*\.(?:png|jpg|jpeg|gif|png|svg))`);
|
var imageRegex = new RegExp(`(http)?s?:?(\/\/[^"']*\.(?:png|jpg|jpeg|gif|png|svg))`);
|
||||||
|
var videoRegex = new RegExp(`(http)?s?:?(\/\/[^"']*\.(?:mp4))`);
|
||||||
|
|
||||||
export function isImage(url: string) {
|
export function isImage(url: string) {
|
||||||
return imageRegex.test(url);
|
return imageRegex.test(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isVideo(url: string) {
|
||||||
|
return videoRegex.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
export function validURL(str: string) {
|
export function validURL(str: string) {
|
||||||
var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
|
var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
|
||||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
|
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
|
||||||
|
|
Reference in a new issue