import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import {
UserOperation,
import autosize from 'autosize';
import { SiteForm } from './site-form';
import { UserListing } from './user-listing';
+import { HtmlTags } from './html-tags';
import { i18n } from '../i18next';
interface AdminSettingsState {
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
import { Component } from 'inferno';
import { Route, Switch } from 'inferno-router';
import { Provider } from 'inferno-i18next';
+import { Helmet } from 'inferno-helmet';
import { i18n } from '../i18next';
import { routes } from '../../shared/routes';
import { Navbar } from '../../shared/components/navbar';
<>
<Provider i18next={i18n}>
<div>
+ {this.props.site.site.icon && (
+ <Helmet>
+ <link
+ id="favicon"
+ rel="icon"
+ type="image/x-icon"
+ href={this.props.site.site.icon}
+ />
+ </Helmet>
+ )}
<Navbar site={this.props.site} />
<div class="mt-4 p-0 fl-1">
<Switch>
import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
+import { HtmlTags } from './html-tags';
import { Subscription } from 'rxjs';
import {
UserOperation,
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
{this.state.loading ? (
<h5 class="">
<svg class="icon icon-spinner spin">
import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { DataType } from '../interfaces';
import {
import { UserService, WebSocketService } from '../services';
import { PostListings } from './post-listings';
import { CommentNodes } from './comment-nodes';
+import { HtmlTags } from './html-tags';
import { SortSelect } from './sort-select';
import { DataTypeSelect } from './data-type-select';
import { Sidebar } from './sidebar';
editPostFindRes,
commentsToFlatNodes,
setupTippy,
- favIconUrl,
notifyPost,
setIsoData,
wsSubscribe,
}
get documentTitle(): string {
- if (this.state.communityRes) {
- return `${this.state.communityRes.community.title} - ${this.state.siteRes.site.name}`;
- } else {
- return 'Lemmy';
- }
- }
-
- get favIcon(): string {
- return this.state.siteRes.site.icon
- ? this.state.siteRes.site.icon
- : favIconUrl;
+ return `${this.state.communityRes.community.title} - ${this.state.siteRes.site.name}`;
}
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle}>
- <link
- id="favicon"
- rel="icon"
- type="image/x-icon"
- href={this.favIcon}
- />
- </Helmet>
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
) : (
<div class="row">
<div class="col-12 col-md-8">
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ description={this.state.communityRes.community.title}
+ image={this.state.communityRes.community.icon}
+ />
{this.communityInfo()}
{this.selects()}
{this.listings()}
import { Component } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { CommunityForm } from './community-form';
+import { HtmlTags } from './html-tags';
import {
Community,
UserOperation,
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
import { Component } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { PostForm } from './post-form';
+import { HtmlTags } from './html-tags';
import {
isBrowser,
lemmyHttp,
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
import { Component } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { PrivateMessageForm } from './private-message-form';
+import { HtmlTags } from './html-tags';
import { UserService, WebSocketService } from '../services';
import {
Site,
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
--- /dev/null
+import { Component } from 'inferno';
+import { Helmet } from 'inferno-helmet';
+import { httpExternalPath } from '../env';
+
+interface HtmlTagsProps {
+ title: string;
+ path: string;
+ description?: string;
+ image?: string;
+}
+
+/// Taken from https://metatags.io/
+export class HtmlTags extends Component<HtmlTagsProps, any> {
+ render() {
+ let url = httpExternalPath(this.props.path);
+
+ return (
+ <Helmet title={this.props.title}>
+ {/* Primary Meta Tags */}
+ <meta name="title" content={this.props.title} />
+ {this.props.description && (
+ <meta name="description" content={this.props.description} />
+ )}
+
+ {/* Open Graph / Facebook */}
+ <meta property="og:type" content="website" />
+ <meta property="og:url" content={url} />
+ <meta property="og:title" content={this.props.title} />
+ {this.props.description && (
+ <meta property="og:description" content={this.props.description} />
+ )}
+ {this.props.image && (
+ <meta property="og:image" content={this.props.image} />
+ )}
+
+ {/* Twitter */}
+ <meta property="twitter:card" content="summary_large_image" />
+ <meta property="twitter:url" content={url} />
+ <meta property="twitter:title" content={this.props.title} />
+ {this.props.description && (
+ <meta
+ property="twitter:description"
+ content={this.props.description}
+ />
+ )}
+ {this.props.image && (
+ <meta property="twitter:image" content={this.props.image} />
+ )}
+ </Helmet>
+ );
+ }
+}
import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import {
UserOperation,
} from '../utils';
import { CommentNodes } from './comment-nodes';
import { PrivateMessage } from './private-message';
+import { HtmlTags } from './html-tags';
import { SortSelect } from './sort-select';
import { i18n } from '../i18next';
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
) : (
<div class="row">
<div class="col-12">
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
<h5 class="mb-1">
{i18n.t('inbox')}
<small>
import { Component } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { GetSiteResponse } from 'lemmy-js-client';
import { setIsoData } from '../utils';
import { i18n } from '../i18next';
+import { HtmlTags } from './html-tags';
interface InstancesState {
siteRes: GetSiteResponse;
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
<div>
<h5>{i18n.t('linked_instances')}</h5>
{this.state.siteRes &&
setIsoData,
} from '../utils';
import { i18n } from '../i18next';
+import { HtmlTags } from './html-tags';
interface State {
loginForm: LoginForm;
}
get documentTitle(): string {
- if (this.state.site.name) {
- return `${i18n.t('login')} - ${this.state.site.name}`;
- } else {
- return 'Lemmy';
- }
+ return `${i18n.t('login')} - ${this.state.site.name}`;
}
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
<div class="row">
<div class="col-12 col-lg-6 mb-4">{this.loginForm()}</div>
<div class="col-12 col-lg-6">{this.registerForm()}</div>
import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Link } from 'inferno-router';
import { Subscription } from 'rxjs';
import {
editPostFindRes,
commentsToFlatNodes,
setupTippy,
- favIconUrl,
notifyPost,
setIsoData,
wsSubscribe,
} from '../utils';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
+import { HtmlTags } from './html-tags';
interface MainState {
subscribedCommunities: CommunityUser[];
return `${this.state.siteRes.site.name}`;
}
- get favIcon(): string {
- return this.state.siteRes.site.icon
- ? this.state.siteRes.site.icon
- : favIconUrl;
- }
-
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle}>
- <link
- id="favicon"
- rel="icon"
- type="image/x-icon"
- href={this.favIcon}
- />
- </Helmet>
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
<div class="row">
<main role="main" class="col-12 col-md-8">
{this.posts()}
import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Link } from 'inferno-router';
import { Subscription } from 'rxjs';
import {
lemmyHttp,
} from '../utils';
import { MomentTime } from './moment-time';
+import { HtmlTags } from './html-tags';
import moment from 'moment';
import { i18n } from '../i18next';
}
get documentTitle(): string {
- if (this.state.site) {
- return `Modlog - ${this.state.site.name}`;
- } else {
- return 'Lemmy';
- }
+ return `Modlog - ${this.state.site.name}`;
}
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
{this.state.loading ? (
<h5 class="">
<svg class="icon icon-spinner spin">
import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import {
UserOperation,
wsSubscribe,
} from '../utils';
import { i18n } from '../i18next';
+import { HtmlTags } from './html-tags';
interface State {
passwordChangeForm: PasswordChangeForm;
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t('password_change')}</h5>
previewLines,
} from '../utils';
import { i18n } from '../i18next';
+import { externalHost } from '../env';
interface PostListingState {
showEdit: boolean;
)}
</li>
<li className="list-inline-item">•</li>
- {post.url && !(hostname(post.url) == window.location.hostname) && (
+ {post.url && !(hostname(post.url) == externalHost) && (
<>
<li className="list-inline-item">
<a
{post.name}
</Link>
)}
- {(isImage(post.url) || this.props.post.thumbnail_url) && (
- <>
- {!this.state.imageExpanded ? (
+ {(isImage(post.url) || this.props.post.thumbnail_url) &&
+ (!this.state.imageExpanded ? (
+ <span
+ class="text-monospace unselectable pointer ml-2 text-muted small"
+ data-tippy-content={i18n.t('expand_here')}
+ onClick={linkEvent(this, this.handleImageExpandClick)}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-plus-square"></use>
+ </svg>
+ </span>
+ ) : (
+ <span>
<span
class="text-monospace unselectable pointer ml-2 text-muted small"
- data-tippy-content={i18n.t('expand_here')}
onClick={linkEvent(this, this.handleImageExpandClick)}
>
<svg class="icon icon-inline">
- <use xlinkHref="#icon-plus-square"></use>
+ <use xlinkHref="#icon-minus-square"></use>
</svg>
</span>
- ) : (
- <span>
+ <div>
<span
- class="text-monospace unselectable pointer ml-2 text-muted small"
+ class="pointer"
onClick={linkEvent(this, this.handleImageExpandClick)}
>
- <svg class="icon icon-inline">
- <use xlinkHref="#icon-minus-square"></use>
- </svg>
+ <img class="img-fluid img-expanded" src={this.getImage()} />
</span>
- <div>
- <span
- class="pointer"
- onClick={linkEvent(this, this.handleImageExpandClick)}
- >
- <img
- class="img-fluid img-expanded"
- src={this.getImage()}
- />
- </span>
- </div>
- </span>
- )}
- </>
- )}
+ </div>
+ </span>
+ ))}
{post.removed && (
<small className="ml-2 text-muted font-italic">
{i18n.t('removed')}
import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
+import { HtmlTags } from './html-tags';
import { Subscription } from 'rxjs';
import {
UserOperation,
createPostLikeRes,
commentsToFlatNodes,
setupTippy,
- favIconUrl,
setIsoData,
getIdFromProps,
getCommentIdFromProps,
setAuth,
lemmyHttp,
isBrowser,
+ previewLines,
+ isImage,
} from '../utils';
import { PostListing } from './post-listing';
import { Sidebar } from './sidebar';
// Necessary if you are on a post and you click another post (same route)
if (_lastProps.location.pathname !== _lastProps.history.location.pathname) {
- // Couldnt get a refresh working. This does for now.
+ // TODO Couldnt get a refresh working. This does for now.
location.reload();
// let currentId = this.props.match.params.id;
}
get documentTitle(): string {
- if (this.state.postRes) {
- return `${this.state.postRes.post.name} - ${this.state.siteRes.site.name}`;
- } else {
- return 'Lemmy';
- }
+ return `${this.state.postRes.post.name} - ${this.state.siteRes.site.name}`;
}
- get favIcon(): string {
- return this.state.siteRes.site.icon
- ? this.state.siteRes.site.icon
- : favIconUrl;
+ get imageTag(): string {
+ return (
+ this.state.postRes.post.thumbnail_url ||
+ (this.state.postRes.post.url
+ ? isImage(this.state.postRes.post.url)
+ ? this.state.postRes.post.url
+ : undefined
+ : undefined)
+ );
+ }
+
+ get descriptionTag(): string {
+ return this.state.postRes.post.body
+ ? previewLines(this.state.postRes.post.body)
+ : undefined;
}
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle}>
- <link
- id="favicon"
- rel="icon"
- type="image/x-icon"
- href={this.favIcon}
- />
- </Helmet>
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
) : (
<div class="row">
<div class="col-12 col-md-8 mb-3">
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ image={this.imageTag}
+ description={this.descriptionTag}
+ />
<PostListing
post={this.state.postRes.post}
showBody
import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import {
UserOperation,
setAuth,
} from '../utils';
import { PostListing } from './post-listing';
+import { HtmlTags } from './html-tags';
import { UserListing } from './user-listing';
import { CommunityLink } from './community-link';
import { SortSelect } from './sort-select';
}
get documentTitle(): string {
- if (this.state.site.name) {
- if (this.state.q) {
- return `${i18n.t('search')} - ${this.state.q} - ${
- this.state.site.name
- }`;
- } else {
- return `${i18n.t('search')} - ${this.state.site.name}`;
- }
+ if (this.state.q) {
+ return `${i18n.t('search')} - ${this.state.q} - ${this.state.site.name}`;
} else {
- return 'Lemmy';
+ return `${i18n.t('search')} - ${this.state.site.name}`;
}
}
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
<h5>{i18n.t('search')}</h5>
{this.selects()}
{this.searchForm()}
import { Component } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Site } from 'lemmy-js-client';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
import { repoUrl, isBrowser } from '../utils';
import { IsoData } from 'shared/interfaces';
+import { HtmlTags } from './html-tags';
interface SilverUser {
name: string;
render() {
return (
<div class="container text-center">
- <Helmet title={this.documentTitle} />
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
{this.topMessage()}
<hr />
{this.sponsors()}
import { Component, linkEvent } from 'inferno';
-import { Helmet } from 'inferno-helmet';
import { Link } from 'inferno-router';
import { Subscription } from 'rxjs';
import {
getLanguage,
mdToHtml,
elementUrl,
- favIconUrl,
setIsoData,
getIdFromProps,
getUsernameFromProps,
createPostLikeFindRes,
setAuth,
lemmyHttp,
+ previewLines,
} from '../utils';
import { UserListing } from './user-listing';
+import { HtmlTags } from './html-tags';
import { SortSelect } from './sort-select';
import { ListingTypeSelect } from './listing-type-select';
import { MomentTime } from './moment-time';
}
get documentTitle(): string {
- if (this.state.siteRes.site.name) {
- return `@${this.state.userName} - ${this.state.siteRes.site.name}`;
- } else {
- return 'Lemmy';
- }
+ return `@${this.state.userName} - ${this.state.siteRes.site.name}`;
}
- get favIcon(): string {
- return this.state.siteRes.site.icon
- ? this.state.siteRes.site.icon
- : favIconUrl;
+ get bioTag(): string {
+ return this.state.userRes.user.bio
+ ? previewLines(this.state.userRes.user.bio)
+ : undefined;
}
render() {
return (
<div class="container">
- <Helmet title={this.documentTitle}>
- <link
- id="favicon"
- rel="icon"
- type="image/x-icon"
- href={this.favIcon}
- />
- </Helmet>
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
<div class="row">
<div class="col-12 col-md-8">
<>
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ description={this.bioTag}
+ image={this.state.userRes.user.avatar}
+ />
{this.userInfo()}
<hr />
</>
import { isBrowser } from './utils';
-const nodeHostname = process.env.LEMMY_HOST || 'localhost'; // used for local dev
-const host = isBrowser() ? window.location.hostname : nodeHostname;
+const testHost = 'localhost:8536';
+
+const internalHost = process.env.LEMMY_INTERNAL_HOST || testHost; // used for local dev
+export const externalHost = isBrowser()
+ ? `${window.location.hostname}:${
+ window.location.port == '1234' || window.location.port == '1235'
+ ? 8536
+ : window.location.port
+ }`
+ : process.env.LEMMY_EXTERNAL_HOST || testHost;
+
+// ? window.location.port == '1234' || window.location.port == '1235'
const secure = isBrowser() && window.location.protocol == 'https:' ? 's' : '';
-const port = isBrowser()
- ? window.location.port == '1234' || window.location.port == '1235'
- ? 8536
- : window.location.port
- : 8536;
-const endpoint = `${host}:${port}`;
-
-export const wsUri = `ws${secure}://${endpoint}/api/v1/ws`;
-export const httpUri = `http${secure}://${endpoint}/api/v1`;
-export const pictrsUri = `http${secure}://${endpoint}/pictrs/image`;
+
+const host = isBrowser() ? externalHost : internalHost;
+
+const httpBase = `http${secure}://${host}`;
+export const wsUri = `ws${secure}://${host}/api/v1/ws`;
+export const httpUri = `${httpBase}/api/v1`;
+const httpExternalUri = `http${secure}://${externalHost}`;
+export const pictrsUri = `${httpBase}/pictrs/image`;
+
+console.log(`Internal host: ${internalHost}`);
+console.log(`External host: ${externalHost}`);
+
+export function httpExternalPath(path: string) {
+ return `${httpExternalUri}${path}`;
+}
+
+// export const httpUri = `http${secure}://${endpoint}/api/v1`;
+// export const pictrsUri = `http${secure}://${endpoint}/pictrs/image`;
+
+// const host = isBrowser() ? window.location.hostname : localHostname;
+// const secure = isBrowser() && window.location.protocol == 'https:' ? 's' : '';
+// const port = isBrowser()
+// ? window.location.port == '1234' || window.location.port == '1235'
+// ? 8536
+// : window.location.port
+// : 8536;
+// const endpoint = `${host}:${port}`;
+//
+// export const wsUri = `ws${secure}://${endpoint}/api/v1/ws`;
+// export const httpUri = `http${secure}://${endpoint}/api/v1`;
+// export const pictrsUri = `http${secure}://${endpoint}/pictrs/image`;
// TODO this broke
export function validURL(str: string) {
- console.log(str);
// try {
return !!new URL(str);
// } catch {
export function setTheme(theme: string, forceReload: boolean = false) {
if (isBrowser() && (theme !== 'darkly' || forceReload)) {
- console.log(`setting theme ${theme}`);
-
// Unload all the other themes
for (var i = 0; i < themes.length; i++) {
let styleSheet = document.getElementById(themes[i]);
export function hostname(url: string): string {
let cUrl = new URL(url);
- // TODO
- return `${cUrl.hostname}:${cUrl.port}`;
- // return window.location.port
- // ? `${cUrl.hostname}:${cUrl.port}`
- // : `${cUrl.hostname}`;
+ return cUrl.port ? `${cUrl.hostname}:${cUrl.port}` : `${cUrl.hostname}`;
}
function canUseWebP() {