auth: String,
}
+ #[derive(Serialize, Deserialize)]
+ pub struct GetSiteConfig {
+ auth: String,
+ }
+
+ #[derive(Serialize, Deserialize)]
+ pub struct GetSiteConfigResponse {
+ config_hjson: String,
+ }
+
+ #[derive(Serialize, Deserialize)]
+ pub struct SaveSiteConfig {
+ config_hjson: String,
+ auth: String,
+ }
+
impl Perform<ListCategoriesResponse> for Oper<ListCategories> {
fn perform(&self, conn: &PgConnection) -> Result<ListCategoriesResponse, Error> {
let _data: &ListCategories = &self.data;
fn perform(&self, conn: &PgConnection) -> Result<GetSiteResponse, Error> {
let _data: &GetSite = &self.data;
- let site = Site::read(&conn, 1);
- let site_view = if site.is_ok() {
+ let site_view = if let Ok(_site) = Site::read(&conn, 1) {
Some(SiteView::read(&conn)?)
} else if let Some(setup) = Settings::get().setup.as_ref() {
let register = Register {
};
let mut admins = UserView::admins(&conn)?;
- if site_view.is_some() {
- let site_creator_id = site_view.to_owned().unwrap().creator_id;
- let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
- let creator_user = admins.remove(creator_index);
- admins.insert(0, creator_user);
+
+ // Make sure the site creator is the top admin
+ if let Some(site_view) = site_view.to_owned() {
+ let site_creator_id = site_view.creator_id;
+ // TODO investigate why this is sometimes coming back null
+ // Maybe user_.admin isn't being set to true?
+ if let Some(creator_index) = admins.iter().position(|r| r.id == site_creator_id) {
+ let creator_user = admins.remove(creator_index);
+ admins.insert(0, creator_user);
+ }
}
let banned = UserView::banned(&conn)?;
})
}
}
+
+ impl Perform<GetSiteConfigResponse> for Oper<GetSiteConfig> {
+ fn perform(&self, conn: &PgConnection) -> Result<GetSiteConfigResponse, Error> {
+ let data: &GetSiteConfig = &self.data;
+
+ let claims = match Claims::decode(&data.auth) {
+ Ok(claims) => claims.claims,
+ Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ };
+
+ let user_id = claims.id;
+
+ // Only let admins read this
+ let admins = UserView::admins(&conn)?;
+ let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
+
+ if !admin_ids.contains(&user_id) {
+ return Err(APIError::err("not_an_admin").into());
+ }
+
+ let config_hjson = Settings::read_config_file()?;
+
+ Ok(GetSiteConfigResponse { config_hjson })
+ }
+ }
+
+ impl Perform<GetSiteConfigResponse> for Oper<SaveSiteConfig> {
+ fn perform(&self, conn: &PgConnection) -> Result<GetSiteConfigResponse, Error> {
+ let data: &SaveSiteConfig = &self.data;
+
+ let claims = match Claims::decode(&data.auth) {
+ Ok(claims) => claims.claims,
+ Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ };
+
+ let user_id = claims.id;
+
+ // Only let admins read this
+ let admins = UserView::admins(&conn)?;
+ let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
+
+ if !admin_ids.contains(&user_id) {
+ return Err(APIError::err("not_an_admin").into());
+ }
+
+ // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
+ let config_hjson = match Settings::save_config_file(&data.config_hjson) {
+ Ok(config_hjson) => config_hjson,
+ Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
+ };
+
+ Ok(GetSiteConfigResponse { config_hjson })
+ }
+ }
pub extern crate jsonwebtoken;
pub extern crate lettre;
pub extern crate lettre_email;
+extern crate log;
+pub extern crate openssl;
pub extern crate rand;
pub extern crate regex;
pub extern crate rss;
pub mod websocket;
use crate::settings::Settings;
-use chrono::{DateTime, NaiveDateTime, Utc};
+use chrono::{DateTime, FixedOffset, Local, NaiveDateTime};
use isahc::prelude::*;
use lettre::smtp::authentication::{Credentials, Mechanism};
use lettre::smtp::extension::ClientId;
use regex::{Regex, RegexBuilder};
use serde::Deserialize;
-pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime<Utc> {
- DateTime::<Utc>::from_utc(ndt, Utc)
-}
-
pub fn naive_now() -> NaiveDateTime {
chrono::prelude::Utc::now().naive_utc()
}
NaiveDateTime::from_timestamp(time, 0)
}
+pub fn convert_datetime(datetime: NaiveDateTime) -> DateTime<FixedOffset> {
+ let now = Local::now();
+ DateTime::<FixedOffset>::from_utc(datetime, *now.offset())
+}
+
pub fn is_email_regex(test: &str) -> bool {
EMAIL_REGEX.is_match(test)
}
to_username: &str,
html: &str,
) -> Result<(), String> {
- let email_config = Settings::get().email.as_ref().ok_or("no_email_setup")?;
+ let email_config = Settings::get().email.ok_or("no_email_setup")?;
let email = Email::builder()
.to((to_email, to_username))
} else {
SmtpClient::new(&email_config.smtp_server, ClientSecurity::None).unwrap()
}
- .hello_name(ClientId::Domain(Settings::get().hostname.to_owned()))
+ .hello_name(ClientId::Domain(Settings::get().hostname))
.smtp_utf8(true)
.authentication_mechanism(Mechanism::Plain)
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited);
use actix_web::*;
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection;
+use failure::Error;
+use lemmy_server::apub::fetcher::fetch_all;
+use lemmy_server::db::code_migrations::run_advanced_migrations;
use lemmy_server::routes::{api, federation, feeds, index, nodeinfo, webfinger, websocket};
use lemmy_server::settings::Settings;
use lemmy_server::websocket::server::*;
-use std::io;
+use log::warn;
+use std::thread;
+use std::thread::sleep;
+use std::time::Duration;
embed_migrations!();
#[actix_rt::main]
-async fn main() -> io::Result<()> {
+async fn main() -> Result<(), Error> {
env_logger::init();
let settings = Settings::get();
// Run the migrations from code
let conn = pool.get().unwrap();
embedded_migrations::run(&conn).unwrap();
+ run_advanced_migrations(&conn).unwrap();
// Set up websocket server
let server = ChatServer::startup(pool.clone()).start();
+ thread::spawn(move || {
+ // some work here
+ sleep(Duration::from_secs(5));
+ println!("Fetching apub data");
+ match fetch_all(&conn) {
+ Ok(_) => {}
+ Err(e) => warn!("Error during apub fetch: {}", e),
+ }
+ });
+
println!(
"Starting http server at {}:{}",
settings.bind, settings.port
);
// Create Http server with websocket support
- HttpServer::new(move || {
- let settings = Settings::get();
- App::new()
- .wrap(middleware::Logger::default())
- .data(pool.clone())
- .data(server.clone())
- // The routes
- .configure(api::config)
- .configure(federation::config)
- .configure(feeds::config)
- .configure(index::config)
- .configure(nodeinfo::config)
- .configure(webfinger::config)
- .configure(websocket::config)
- // static files
- .service(actix_files::Files::new(
- "/static",
- settings.front_end_dir.to_owned(),
- ))
- .service(actix_files::Files::new(
- "/docs",
- settings.front_end_dir + "/documentation",
- ))
- })
- .bind((settings.bind, settings.port))?
- .run()
- .await
+ Ok(
+ HttpServer::new(move || {
++ let settings = Settings::get();
+ App::new()
+ .wrap(middleware::Logger::default())
+ .data(pool.clone())
+ .data(server.clone())
+ // The routes
+ .configure(api::config)
+ .configure(federation::config)
+ .configure(feeds::config)
+ .configure(index::config)
+ .configure(nodeinfo::config)
+ .configure(webfinger::config)
+ .configure(websocket::config)
+ // static files
+ .service(actix_files::Files::new(
+ "/static",
+ settings.front_end_dir.to_owned(),
+ ))
+ .service(actix_files::Files::new(
+ "/docs",
+ settings.front_end_dir.to_owned() + "/documentation",
+ ))
+ })
+ .bind((settings.bind, settings.port))?
+ .run()
+ .await?,
+ )
}
use config::{Config, ConfigError, Environment, File};
+ use failure::Error;
use serde::Deserialize;
use std::env;
+ use std::fs;
use std::net::IpAddr;
+ use std::sync::RwLock;
static CONFIG_FILE_DEFAULTS: &str = "config/defaults.hjson";
static CONFIG_FILE: &str = "config/config.hjson";
- #[derive(Debug, Deserialize)]
+ #[derive(Debug, Deserialize, Clone)]
pub struct Settings {
pub setup: Option<Setup>,
pub database: Database,
pub front_end_dir: String,
pub rate_limit: RateLimitConfig,
pub email: Option<EmailConfig>,
- pub federation_enabled: bool,
+ pub federation: Federation,
}
- #[derive(Debug, Deserialize)]
+ #[derive(Debug, Deserialize, Clone)]
pub struct Setup {
pub admin_username: String,
pub admin_password: String,
pub site_name: String,
}
- #[derive(Debug, Deserialize)]
+ #[derive(Debug, Deserialize, Clone)]
pub struct RateLimitConfig {
pub message: i32,
pub message_per_second: i32,
pub register_per_second: i32,
}
- #[derive(Debug, Deserialize)]
+ #[derive(Debug, Deserialize, Clone)]
pub struct EmailConfig {
pub smtp_server: String,
pub smtp_login: Option<String>,
pub use_tls: bool,
}
- #[derive(Debug, Deserialize)]
+ #[derive(Debug, Deserialize, Clone)]
pub struct Database {
pub user: String,
pub password: String,
pub pool_size: u32,
}
+#[derive(Debug, Deserialize)]
+pub struct Federation {
+ pub enabled: bool,
+ pub followed_instances: String,
+ pub tls_enabled: bool,
+}
+
lazy_static! {
- static ref SETTINGS: Settings = {
- match Settings::init() {
- Ok(c) => c,
- Err(e) => panic!("{}", e),
- }
- };
+ static ref SETTINGS: RwLock<Settings> = RwLock::new(match Settings::init() {
+ Ok(c) => c,
+ Err(e) => panic!("{}", e),
+ });
}
impl Settings {
}
/// Returns the config as a struct.
- pub fn get() -> &'static Self {
- &SETTINGS
+ pub fn get() -> Self {
+ SETTINGS.read().unwrap().to_owned()
}
/// Returns the postgres connection url. If LEMMY_DATABASE_URL is set, that is used,
pub fn api_endpoint(&self) -> String {
format!("{}/api/v1", self.hostname)
}
+
+ pub fn read_config_file() -> Result<String, Error> {
+ Ok(fs::read_to_string(CONFIG_FILE)?)
+ }
+
+ pub fn save_config_file(data: &str) -> Result<String, Error> {
+ fs::write(CONFIG_FILE, data)?;
+
+ // Reload the new settings
+ // From https://stackoverflow.com/questions/29654927/how-do-i-assign-a-string-to-a-mutable-static-variable/47181804#47181804
+ let mut new_settings = SETTINGS.write().unwrap();
+ *new_settings = match Settings::init() {
+ Ok(c) => c,
+ Err(e) => panic!("{}", e),
+ };
+
+ Self::read_config_file()
+ }
}
let user_operation: UserOperation = UserOperation::from_str(&op)?;
- // TODO: none of the chat messages are going to work if stuff is submitted via http api,
- // need to move that handling elsewhere
-
// A DDOS check
chat.check_rate_limit_message(msg.id, false)?;
}
UserOperation::GetCommunity => {
let get_community: GetCommunity = serde_json::from_str(data)?;
+
let mut res = Oper::new(get_community).perform(&conn)?;
+
let community_id = res.community.id;
chat.join_community_room(community_id, msg.id);
}
UserOperation::GetPosts => {
let get_posts: GetPosts = serde_json::from_str(data)?;
+
if get_posts.community_id.is_none() {
// 0 is the "all" community
chat.join_community_room(0, msg.id);
res.online = chat.sessions.len();
to_json_string(&user_operation, &res)
}
+ UserOperation::GetSiteConfig => {
+ let get_site_config: GetSiteConfig = serde_json::from_str(data)?;
+ let res = Oper::new(get_site_config).perform(&conn)?;
+ to_json_string(&user_operation, &res)
+ }
+ UserOperation::SaveSiteConfig => {
+ let save_site_config: SaveSiteConfig = serde_json::from_str(data)?;
+ let res = Oper::new(save_site_config).perform(&conn)?;
+ to_json_string(&user_operation, &res)
+ }
UserOperation::Search => {
do_user_operation::<Search, SearchResponse>(user_operation, data, &conn)
}
},
"keywords": [],
"dependencies": {
+ "@joeattardi/emoji-button": "^2.12.1",
"@types/autosize": "^3.0.6",
- "@types/js-cookie": "^2.2.5",
+ "@types/js-cookie": "^2.2.6",
"@types/jwt-decode": "^2.2.1",
- "@types/markdown-it": "^10.0.0",
+ "@types/markdown-it": "^0.0.9",
"@types/markdown-it-container": "^2.0.2",
- "@types/node": "^13.9.2",
+ "@types/node": "^13.11.1",
"autosize": "^4.0.2",
"bootswatch": "^4.3.1",
- "classcat": "^1.1.3",
+ "classcat": "^4.0.2",
"dotenv": "^8.2.0",
"emoji-short-name": "^1.0.0",
- "husky": "^4.2.3",
- "i18next": "^19.3.3",
+ "husky": "^4.2.5",
+ "i18next": "^19.4.1",
"inferno": "^7.4.2",
"inferno-i18next": "nimbusec-oss/inferno-i18next",
"inferno-router": "^7.4.2",
"markdown-it-emoji": "^1.4.0",
"mobius1-selectr": "^2.4.13",
"moment": "^2.24.0",
- "prettier": "^1.18.2",
+ "prettier": "^2.0.4",
"reconnecting-websocket": "^4.4.0",
- "rxjs": "^6.4.0",
- "terser": "^4.6.7",
- "tippy.js": "^6.1.0",
+ "rxjs": "^6.5.5",
+ "terser": "^4.6.11",
+ "tippy.js": "^6.1.1",
"toastify-js": "^1.7.0",
- "tributejs": "^5.1.2",
+ "tributejs": "^5.1.3",
"twemoji": "^12.1.2",
"ws": "^7.2.3"
},
"devDependencies": {
"eslint": "^6.5.1",
"eslint-plugin-inferno": "^7.14.3",
- "eslint-plugin-jane": "^7.2.0",
+ "eslint-plugin-jane": "^7.2.1",
"fuse-box": "^3.1.3",
- "lint-staged": "^10.0.8",
- "sortpack": "^2.1.2",
- "ts-node": "^8.7.0",
- "ts-transform-classcat": "^0.0.2",
- "ts-transform-inferno": "^4.0.2",
+ "lint-staged": "^10.1.3",
+ "sortpack": "^2.1.4",
+ "ts-node": "^8.8.2",
+ "ts-transform-classcat": "^1.0.0",
+ "ts-transform-inferno": "^4.0.3",
"typescript": "^3.8.3"
},
"engines": {
toast,
setupTribute,
wsJsonToRes,
+ emojiPicker,
} from '../utils';
import { WebSocketService, UserService } from '../services';
import autosize from 'autosize';
import Tribute from 'tributejs/src/Tribute.js';
+ import emojiShortName from 'emoji-short-name';
import { i18n } from '../i18next';
interface CommentFormProps {
super(props, context);
this.tribute = setupTribute();
+ this.setupEmojiPicker();
+
this.state = this.emptyState;
if (this.props.node) {
</button>
{this.state.commentForm.content && (
<button
-- className={`btn btn-sm mr-2 btn-secondary ${this.state
-- .previewMode && 'active'}`}
++ className={`btn btn-sm mr-2 btn-secondary ${
++ this.state.previewMode && 'active'
++ }`}
onClick={linkEvent(this, this.handlePreviewToggle)}
>
{i18n.t('preview')}
<use xlinkHref="#icon-spinner"></use>
</svg>
)}
+ <span
+ onClick={linkEvent(this, this.handleEmojiPickerClick)}
+ class="pointer unselectable d-inline-block mr-3 float-right text-muted font-weight-bold"
+ data-tippy-content={i18n.t('emoji_picker')}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-smile"></use>
+ </svg>
+ </span>
</div>
</div>
</form>
);
}
+ setupEmojiPicker() {
+ emojiPicker.on('emoji', twemojiHtmlStr => {
+ if (this.state.commentForm.content == null) {
+ this.state.commentForm.content = '';
+ }
+ var el = document.createElement('div');
+ el.innerHTML = twemojiHtmlStr;
+ let nativeUnicode = (el.childNodes[0] as HTMLElement).getAttribute('alt');
+ let shortName = `:${emojiShortName[nativeUnicode]}:`;
+ this.state.commentForm.content += shortName;
+ this.setState(this.state);
+ });
+ }
+
handleFinished() {
this.state.previewMode = false;
this.state.loading = false;
i.setState(i.state);
}
+ handleEmojiPickerClick(_i: CommentForm, event: any) {
+ emojiPicker.togglePicker(event.target);
+ }
+
handleCommentContentChange(i: CommentForm, event: any) {
i.state.commentForm.content = event.target.value;
i.setState(i.state);
getUnixTime,
canMod,
isMod,
- pictshareAvatarThumbnail,
- showAvatars,
setupTippy,
colorList,
} from '../utils';
import { MomentTime } from './moment-time';
import { CommentForm } from './comment-form';
import { CommentNodes } from './comment-nodes';
+ import { UserListing } from './user-listing';
import { i18n } from '../i18next';
interface CommentNodeState {
}
>
<div
-- class={`${!this.props.noIndent &&
++ class={`${
++ !this.props.noIndent &&
this.props.node.comment.parent_id &&
-- 'ml-2'}`}
++ 'ml-2'
++ }`}
>
<div class="d-flex flex-wrap align-items-center mb-1 mt-1 text-muted small">
- <Link
- className="mr-2 text-body font-weight-bold"
- to={`/u/${node.comment.creator_name}`}
- >
- {node.comment.creator_avatar && showAvatars() && (
- <img
- height="32"
- width="32"
- src={pictshareAvatarThumbnail(node.comment.creator_avatar)}
- class="rounded-circle mr-1"
- />
- )}
- <span>{node.comment.creator_name}</span>
- </Link>
+ <span class="mr-2">
+ <UserListing
+ user={{
+ name: node.comment.creator_name,
+ avatar: node.comment.creator_avatar,
+ }}
+ />
+ </span>
{this.isMod && (
<div className="badge badge-light d-none d-sm-inline mr-2">
{i18n.t('mod')}
</>
)}
<div
- className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mr-2"
+ className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"
onClick={linkEvent(this, this.handleCommentCollapse)}
>
{this.state.collapsed ? (
this.loadingIcon
) : (
<svg
-- class={`icon icon-inline ${node.comment.read &&
-- 'text-success'}`}
++ class={`icon icon-inline ${
++ node.comment.read && 'text-success'
++ }`}
>
<use xlinkHref="#icon-check"></use>
</svg>
this.loadingIcon
) : (
<svg
-- class={`icon icon-inline ${node.comment.saved &&
-- 'text-warning'}`}
++ class={`icon icon-inline ${
++ node.comment.saved && 'text-warning'
++ }`}
>
<use xlinkHref="#icon-star"></use>
</svg>
data-tippy-content={i18n.t('view_source')}
>
<svg
-- class={`icon icon-inline ${this.state
-- .viewSource && 'text-success'}`}
++ class={`icon icon-inline ${
++ this.state.viewSource && 'text-success'
++ }`}
>
<use xlinkHref="#icon-file-text"></use>
</svg>
}
>
<svg
-- class={`icon icon-inline ${node.comment
-- .deleted && 'text-danger'}`}
++ class={`icon icon-inline ${
++ node.comment.deleted && 'text-danger'
++ }`}
>
<use xlinkHref="#icon-trash"></use>
</svg>
Comment,
CommentResponse,
PrivateMessage,
+ UserView,
PrivateMessageResponse,
WebSocketJsonResponse,
} from '../interfaces';
messages: Array<PrivateMessage>;
unreadCount: number;
siteName: string;
+ admins: Array<UserView>;
}
export class Navbar extends Component<any, NavbarState> {
messages: [],
expanded: false,
siteName: undefined,
+ admins: [],
};
constructor(props: any, context: any) {
</li>
</ul>
<ul class="navbar-nav ml-auto">
+ {this.canAdmin && (
+ <li className="nav-item mt-1">
+ <Link
+ class="nav-link"
+ to={`/admin`}
+ title={i18n.t('admin_settings')}
+ >
+ <svg class="icon">
+ <use xlinkHref="#icon-settings"></use>
+ </svg>
+ </Link>
+ </li>
+ )}
{this.state.isLoggedIn ? (
<>
<li className="nav-item mt-1">
if (data.site && !this.state.siteName) {
this.state.siteName = data.site.name;
+ this.state.admins = data.admins;
WebSocketService.Instance.site = data.site;
+ WebSocketService.Instance.admins = data.admins;
+
this.setState(this.state);
}
}
);
}
+ get canAdmin(): boolean {
+ return (
+ UserService.Instance.user &&
+ this.state.admins.map(a => a.id).includes(UserService.Instance.user.id)
+ );
+ }
+
requestNotificationPermission() {
if (UserService.Instance.user) {
-- document.addEventListener('DOMContentLoaded', function() {
++ document.addEventListener('DOMContentLoaded', function () {
if (!Notification) {
toast(i18n.t('notifications_error'), 'danger');
return;
randomStr,
setupTribute,
setupTippy,
+ emojiPicker,
} from '../utils';
import autosize from 'autosize';
import Tribute from 'tributejs/src/Tribute.js';
+ import emojiShortName from 'emoji-short-name';
import Selectr from 'mobius1-selectr';
import { i18n } from '../i18next';
this.fetchPageTitle = debounce(this.fetchPageTitle).bind(this);
this.tribute = setupTribute();
+ this.setupEmojiPicker();
+
this.state = this.emptyState;
if (this.props.post) {
<form>
<label
htmlFor="file-upload"
-- className={`${UserService.Instance.user &&
- 'pointer'} d-inline-block float-right text-muted h6 font-weight-bold`}
- 'pointer'} d-inline-block float-right text-muted font-weight-bold`}
++ className={`${
++ UserService.Instance.user && 'pointer'
++ } d-inline-block float-right text-muted font-weight-bold`}
data-tippy-content={i18n.t('upload_image')}
>
<svg class="icon icon-inline">
)}
{this.state.postForm.body && (
<button
-- className={`mt-1 mr-2 btn btn-sm btn-secondary ${this.state
-- .previewMode && 'active'}`}
++ className={`mt-1 mr-2 btn btn-sm btn-secondary ${
++ this.state.previewMode && 'active'
++ }`}
onClick={linkEvent(this, this.handlePreviewToggle)}
>
{i18n.t('preview')}
<a
href={markdownHelpUrl}
target="_blank"
- class="d-inline-block float-right text-muted h6 font-weight-bold"
+ class="d-inline-block float-right text-muted font-weight-bold"
title={i18n.t('formatting_help')}
>
<svg class="icon icon-inline">
<use xlinkHref="#icon-help-circle"></use>
</svg>
</a>
+ <span
+ onClick={linkEvent(this, this.handleEmojiPickerClick)}
+ class="pointer unselectable d-inline-block mr-3 float-right text-muted font-weight-bold"
+ data-tippy-content={i18n.t('emoji_picker')}
+ >
+ <svg class="icon icon-inline">
+ <use xlinkHref="#icon-smile"></use>
+ </svg>
+ </span>
</div>
</div>
{!this.props.post && (
);
}
+ setupEmojiPicker() {
+ emojiPicker.on('emoji', twemojiHtmlStr => {
+ if (this.state.postForm.body == null) {
+ this.state.postForm.body = '';
+ }
+ var el = document.createElement('div');
+ el.innerHTML = twemojiHtmlStr;
+ let nativeUnicode = (el.childNodes[0] as HTMLElement).getAttribute('alt');
+ let shortName = `:${emojiShortName[nativeUnicode]}:`;
+ this.state.postForm.body += shortName;
+ this.setState(this.state);
+ });
+ }
+
handlePostSubmit(i: PostForm, event: any) {
event.preventDefault();
if (i.props.post) {
});
}
+ handleEmojiPickerClick(_i: PostForm, event: any) {
+ emojiPicker.togglePicker(event.target);
+ }
+
parseMessage(msg: WebSocketJsonResponse) {
let res = wsJsonToRes(msg);
if (msg.error) {
import { MomentTime } from './moment-time';
import { PostForm } from './post-form';
import { IFramelyCard } from './iframely-card';
+ import { UserListing } from './user-listing';
import {
+ md,
mdToHtml,
canMod,
isMod,
isImage,
isVideo,
getUnixTime,
- pictshareAvatarThumbnail,
- showAvatars,
pictshareImage,
setupTippy,
+ hostname,
+ previewLines,
} from '../utils';
import { i18n } from '../i18next';
let post = this.props.post;
return (
<img
- className={`img-fluid thumbnail rounded ${(post.nsfw ||
- post.community_nsfw) &&
- 'img-blur'}`}
+ className={`img-fluid thumbnail rounded ${
+ (post.nsfw || post.community_nsfw) && 'img-blur'
+ }`}
src={src}
/>
);
</Link>
)}
</h5>
- {post.url &&
- !(new URL(post.url).hostname == window.location.hostname) && (
- <small class="d-inline-block">
- <a
- className="ml-2 text-muted font-italic"
- href={post.url}
- target="_blank"
- title={post.url}
- >
- {new URL(post.url).hostname}
- <svg class="ml-1 icon icon-inline">
- <use xlinkHref="#icon-external-link"></use>
- </svg>
- </a>
- </small>
- )}
+ {post.url && !(hostname(post.url) == window.location.hostname) && (
+ <small class="d-inline-block">
+ <a
+ className="ml-2 text-muted font-italic"
+ href={post.url}
+ target="_blank"
+ title={post.url}
+ >
+ {hostname(post.url)}
+ <svg class="ml-1 icon icon-inline">
+ <use xlinkHref="#icon-external-link"></use>
+ </svg>
+ </a>
+ </small>
+ )}
{(isImage(post.url) || this.props.post.thumbnail_url) && (
<>
{!this.state.imageExpanded ? (
<ul class="list-inline mb-0 text-muted small">
<li className="list-inline-item">
<span>{i18n.t('by')} </span>
- <Link
- className="text-body font-weight-bold"
- to={`/u/${post.creator_name}`}
- >
- {post.creator_avatar && showAvatars() && (
- <img
- height="32"
- width="32"
- src={pictshareAvatarThumbnail(post.creator_avatar)}
- class="rounded-circle mr-1"
- />
- )}
- <span>{post.creator_name}</span>
- </Link>
+ <UserListing
+ user={{
+ name: post.creator_name,
+ avatar: post.creator_avatar,
+ }}
+ />
{this.isMod && (
<span className="mx-1 badge badge-light">
{i18n.t('mod')}
{this.props.showCommunity && (
<span>
<span> {i18n.t('to')} </span>
- <Link to={`/c/${post.community_name}`}>
- {post.community_name}
- </Link>
+ {post.local ? (
+ <Link to={`/c/${post.community_name}`}>
+ {post.community_name}
+ </Link>
+ ) : (
+ <a href={post.community_actor_id} target="_blank">
+ {hostname(post.ap_id)}/{post.community_name}
+ </a>
+ )}
</span>
)}
</li>
<MomentTime data={post} />
</span>
</li>
+ {post.body && (
+ <>
+ <li className="list-inline-item">•</li>
+ <li className="list-inline-item">
+ {/* Using a link with tippy doesn't work on touch devices unfortunately */}
+ <Link
+ className="text-muted"
+ data-tippy-content={md.render(previewLines(post.body))}
+ data-tippy-allowHtml={true}
+ to={`/post/${post.id}`}
+ >
+ <svg class="mr-1 icon icon-inline">
+ <use xlinkHref="#icon-book-open"></use>
+ </svg>
+ </Link>
+ </li>
+ </>
+ )}
<li className="list-inline-item">•</li>
{this.state.upvotes !== this.state.score && (
<>
}
>
<svg
- class={`icon icon-inline ${post.saved &&
- 'text-warning'}`}
+ class={`icon icon-inline ${
+ post.saved && 'text-warning'
+ }`}
>
<use xlinkHref="#icon-star"></use>
</svg>
}
>
<svg
- class={`icon icon-inline ${post.deleted &&
- 'text-danger'}`}
+ class={`icon icon-inline ${
+ post.deleted && 'text-danger'
+ }`}
>
<use xlinkHref="#icon-trash"></use>
</svg>
data-tippy-content={i18n.t('view_source')}
>
<svg
- class={`icon icon-inline ${this.state
- .viewSource && 'text-success'}`}
+ class={`icon icon-inline ${
+ this.state.viewSource && 'text-success'
+ }`}
>
<use xlinkHref="#icon-file-text"></use>
</svg>
}
>
<svg
- class={`icon icon-inline ${post.locked &&
- 'text-danger'}`}
+ class={`icon icon-inline ${
+ post.locked && 'text-danger'
+ }`}
>
<use xlinkHref="#icon-lock"></use>
</svg>
}
>
<svg
- class={`icon icon-inline ${post.stickied &&
- 'text-success'}`}
+ class={`icon icon-inline ${
+ post.stickied && 'text-success'
+ }`}
>
<use xlinkHref="#icon-pin"></use>
</svg>
return (
<div class="btn-group btn-group-toggle mb-2">
<label
-- className={`btn btn-sm btn-secondary pointer ${this.state
-- .commentSort === CommentSortType.Hot && 'active'}`}
++ className={`btn btn-sm btn-secondary pointer ${
++ this.state.commentSort === CommentSortType.Hot && 'active'
++ }`}
>
{i18n.t('hot')}
<input
/>
</label>
<label
-- className={`btn btn-sm btn-secondary pointer ${this.state
-- .commentSort === CommentSortType.Top && 'active'}`}
++ className={`btn btn-sm btn-secondary pointer ${
++ this.state.commentSort === CommentSortType.Top && 'active'
++ }`}
>
{i18n.t('top')}
<input
/>
</label>
<label
-- className={`btn btn-sm btn-secondary pointer ${this.state
-- .commentSort === CommentSortType.New && 'active'}`}
++ className={`btn btn-sm btn-secondary pointer ${
++ this.state.commentSort === CommentSortType.New && 'active'
++ }`}
>
{i18n.t('new')}
<input
/>
</label>
<label
-- className={`btn btn-sm btn-secondary pointer ${this.state
-- .commentSort === CommentSortType.Old && 'active'}`}
++ className={`btn btn-sm btn-secondary pointer ${
++ this.state.commentSort === CommentSortType.Old && 'active'
++ }`}
>
{i18n.t('old')}
<input
} else if (res.op == UserOperation.Search) {
let data = res.data as SearchResponse;
this.state.crossPosts = data.posts.filter(
- p => p.id != this.state.post.id
+ p => p.id != Number(this.props.match.params.id)
);
this.setState(this.state);
} else if (res.op == UserOperation.TransferSite) {
capitalizeFirstLetter,
markdownHelpUrl,
mdToHtml,
- showAvatars,
- pictshareAvatarThumbnail,
wsJsonToRes,
toast,
randomStr,
setupTribute,
setupTippy,
} from '../utils';
+ import { UserListing } from './user-listing';
import Tribute from 'tributejs/src/Tribute.js';
import autosize from 'autosize';
import { i18n } from '../i18next';
{this.state.recipient && (
<div class="col-sm-10 form-control-plaintext">
- <Link
- className="text-body font-weight-bold"
- to={`/u/${this.state.recipient.name}`}
- >
- {this.state.recipient.avatar && showAvatars() && (
- <img
- height="32"
- width="32"
- src={pictshareAvatarThumbnail(
- this.state.recipient.avatar
- )}
- class="rounded-circle mr-1"
- />
- )}
- <span>{this.state.recipient.name}</span>
- </Link>
+ <UserListing
+ user={{
+ name: this.state.recipient.name,
+ avatar: this.state.recipient.avatar,
+ }}
+ />
</div>
)}
</div>
</button>
{this.state.privateMessageForm.content && (
<button
-- className={`btn btn-secondary mr-2 ${this.state.previewMode &&
-- 'active'}`}
++ className={`btn btn-secondary mr-2 ${
++ this.state.previewMode && 'active'
++ }`}
onClick={linkEvent(this, this.handlePreviewToggle)}
>
{i18n.t('preview')}
<div class="border-top border-light">
<div>
<ul class="list-inline mb-0 text-muted small">
+ {/* TODO refactor this */}
<li className="list-inline-item">
{this.mine ? i18n.t('to') : i18n.t('from')}
</li>
}
>
<svg
-- class={`icon icon-inline ${message.read &&
-- 'text-success'}`}
++ class={`icon icon-inline ${
++ message.read && 'text-success'
++ }`}
>
<use xlinkHref="#icon-check"></use>
</svg>
}
>
<svg
-- class={`icon icon-inline ${message.deleted &&
-- 'text-danger'}`}
++ class={`icon icon-inline ${
++ message.deleted && 'text-danger'
++ }`}
>
<use xlinkHref="#icon-trash"></use>
</svg>
data-tippy-content={i18n.t('view_source')}
>
<svg
-- class={`icon icon-inline ${this.state.viewSource &&
-- 'text-success'}`}
++ class={`icon icon-inline ${
++ this.state.viewSource && 'text-success'
++ }`}
>
<use xlinkHref="#icon-file-text"></use>
</svg>
showAvatars,
} from '../utils';
import { CommunityForm } from './community-form';
+ import { UserListing } from './user-listing';
import { i18n } from '../i18next';
interface SidebarProps {
}
>
<svg
-- class={`icon icon-inline ${community.deleted &&
-- 'text-danger'}`}
++ class={`icon icon-inline ${
++ community.deleted && 'text-danger'
++ }`}
>
<use xlinkHref="#icon-trash"></use>
</svg>
<li class="list-inline-item">{i18n.t('mods')}: </li>
{this.props.moderators.map(mod => (
<li class="list-inline-item">
- <Link
- class="text-body font-weight-bold"
- to={`/u/${mod.user_name}`}
- >
- {mod.avatar && showAvatars() && (
- <img
- height="32"
- width="32"
- src={pictshareAvatarThumbnail(mod.avatar)}
- class="rounded-circle mr-1"
- />
- )}
- <span>{mod.user_name}</span>
- </Link>
+ <UserListing
+ user={{
+ name: mod.user_name,
+ avatar: mod.avatar,
+ }}
+ />
</li>
))}
</ul>
<Link
-- class={`btn btn-sm btn-secondary btn-block mb-3 ${(community.deleted ||
-- community.removed) &&
-- 'no-click'}`}
++ class={`btn btn-sm btn-secondary btn-block mb-3 ${
++ (community.deleted || community.removed) && 'no-click'
++ }`}
to={`/create_post?community=${community.name}`}
>
{i18n.t('create_a_post')}
GetPrivateMessages,
UserJoin,
GetComments,
+ GetSiteConfig,
+ SaveSiteConfig,
}
export enum CommentSortType {
export interface UserView {
id: number;
+ actor_id: string;
name: string;
avatar?: string;
email?: string;
matrix_user_id?: string;
+ bio?: string;
+ local: boolean;
published: string;
number_of_posts: number;
post_score: number;
export interface CommunityUser {
id: number;
user_id: number;
+ user_actor_id: string;
+ user_local: boolean;
user_name: string;
avatar?: string;
community_id: number;
+ community_actor_id: string;
+ community_local: boolean;
community_name: string;
published: string;
}
export interface Community {
id: number;
+ actor_id: string;
+ local: boolean;
name: string;
title: string;
description?: string;
nsfw: boolean;
published: string;
updated?: string;
+ creator_actor_id: string;
+ creator_local: boolean;
+ last_refreshed_at: string;
creator_name: string;
creator_avatar?: string;
category_name: string;
embed_description?: string;
embed_html?: string;
thumbnail_url?: string;
+ ap_id: string;
+ local: boolean;
nsfw: boolean;
banned: boolean;
banned_from_community: boolean;
published: string;
updated?: string;
+ creator_actor_id: string;
+ creator_local: boolean;
creator_name: string;
creator_avatar?: string;
+ community_actor_id: string;
+ community_local: boolean;
community_name: string;
community_removed: boolean;
community_deleted: boolean;
export interface Comment {
id: number;
+ ap_id: string;
+ local: boolean;
creator_id: number;
post_id: number;
parent_id?: number;
published: string;
updated?: string;
community_id: number;
+ community_actor_id: string;
+ community_local: boolean;
community_name: string;
banned: boolean;
banned_from_community: boolean;
+ creator_actor_id: string;
+ creator_local: boolean;
creator_name: string;
creator_avatar?: string;
score: number;
saved?: boolean;
user_mention_id?: number; // For mention type
recipient_id?: number;
+ recipient_actor_id?: string;
+ recipient_local?: boolean;
depth?: number;
}
auth?: string;
}
+ export interface GetSiteConfig {
+ auth?: string;
+ }
+
+ export interface GetSiteConfigResponse {
+ config_hjson: string;
+ }
+
+ export interface SiteConfigForm {
+ config_hjson: string;
+ auth?: string;
+ }
+
export interface GetSiteResponse {
site: Site;
admins: Array<UserView>;
| PasswordChangeForm
| PrivateMessageForm
| EditPrivateMessageForm
- | GetPrivateMessagesForm;
+ | GetPrivateMessagesForm
+ | SiteConfigForm;
type ResponseType =
| SiteResponse
| BanUserResponse
| AddAdminResponse
| PrivateMessageResponse
- | PrivateMessagesResponse;
+ | PrivateMessagesResponse
+ | GetSiteConfigResponse;
export interface WebSocketResponse {
op: UserOperation;
import emojiShortName from 'emoji-short-name';
import Toastify from 'toastify-js';
import tippy from 'tippy.js';
+ import EmojiButton from '@joeattardi/emoji-button';
- export const repoUrl = 'https://github.com/dessalines/lemmy';
+ export const repoUrl = 'https://github.com/LemmyNet/lemmy';
export const helpGuideUrl = '/docs/about_guide.html';
export const markdownHelpUrl = `${helpGuideUrl}#markdown-guide`;
export const sortingHelpUrl = `${helpGuideUrl}#sorting`;
'i386',
];
+ export const emojiPicker = new EmojiButton({
+ // Use the emojiShortName from native
+ style: 'twemoji',
+ theme: 'dark',
+ position: 'auto-start',
+ // TODO i18n
+ });
+
export function randomStr() {
return Math.random()
.toString(36)
typographer: true,
})
.use(markdown_it_container, 'spoiler', {
- validate: function(params: any) {
+ validate: function (params: any) {
return params.trim().match(/^spoiler\s+(.*)$/);
},
- render: function(tokens: any, idx: any) {
+ render: function (tokens: any, idx: any) {
var m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/);
if (tokens[idx].nesting === 1) {
defs: objectFlip(emojiShortName),
});
-md.renderer.rules.emoji = function(token, idx) {
+md.renderer.rules.emoji = function (token, idx) {
return twemoji.parse(token[idx].content);
};
let timeout: any;
// Calling debounce returns a new anonymous function
- return function() {
+ return function () {
// reference the context and args for the setTimeout function
var context = this,
args = arguments;
clearTimeout(timeout);
// Set the new timeout
- timeout = setTimeout(function() {
+ timeout = setTimeout(function () {
// Inside the timeout function, clear the timeout variable
// which will let the next execution run when in 'immediate' mode
timeout = null;
{
trigger: ':',
menuItemTemplate: (item: any) => {
- let emoji = `:${item.original.key}:`;
- return `${item.original.val} ${emoji}`;
+ let shortName = `:${item.original.key}:`;
+ let twemojiIcon = twemoji.parse(item.original.val);
+ return `${twemojiIcon} ${shortName}`;
},
selectTemplate: (item: any) => {
return `:${item.original.key}:`;
return `hsla(${Math.random() * 360}, 100%, 50%, 1)`;
}
+ export function previewLines(text: string, lines: number = 3): string {
+ // Use lines * 2 because markdown requires 2 lines
+ return text
+ .split('\n')
+ .slice(0, lines * 2)
+ .join('\n');
+ }
++
+export function hostname(url: string): string {
+ return new URL(url).hostname;
+}
lodash "^4.17.13"
to-fast-properties "^2.0.0"
- "@popperjs/core@^2.1.1":
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.1.1.tgz#12c572ab88ef7345b43f21883fca26631c223085"
- integrity sha512-sLqWxCzC5/QHLhziXSCAksBxHfOnQlhPRVgPK0egEw+ktWvG75T2k+aYWVjVh9+WKeT3tlG3ZNbZQvZLmfuOIw==
+ "@fortawesome/fontawesome-common-types@^0.2.28":
+ version "0.2.28"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.28.tgz#1091bdfe63b3f139441e9cba27aa022bff97d8b2"
+ integrity sha512-gtis2/5yLdfI6n0ia0jH7NJs5i/Z/8M/ZbQL6jXQhCthEOe5Cr5NcQPhgTvFxNOtURE03/ZqUcEskdn2M+QaBg==
+
+ "@fortawesome/fontawesome-svg-core@^1.2.22":
+ version "1.2.28"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.28.tgz#e5b8c8814ef375f01f5d7c132d3c3a2f83a3abf9"
+ integrity sha512-4LeaNHWvrneoU0i8b5RTOJHKx7E+y7jYejplR7uSVB34+mp3Veg7cbKk7NBCLiI4TyoWS1wh9ZdoyLJR8wSAdg==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "^0.2.28"
+
+ "@fortawesome/free-regular-svg-icons@^5.10.2":
+ version "5.13.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.0.tgz#925a13d8bdda0678f71551828cac80ab47b8150c"
+ integrity sha512-70FAyiS5j+ANYD4dh9NGowTorNDnyvQHHpCM7FpnF7GxtDjBUCKdrFqCPzesEIpNDFNd+La3vex+jDk4nnUfpA==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "^0.2.28"
+
+ "@fortawesome/free-solid-svg-icons@^5.10.2":
+ version "5.13.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.0.tgz#44d9118668ad96b4fd5c9434a43efc5903525739"
+ integrity sha512-IHUgDJdomv6YtG4p3zl1B5wWf9ffinHIvebqQOmV3U+3SLw4fC+LUCCgwfETkbTtjy5/Qws2VoVf6z/ETQpFpg==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "^0.2.28"
+
+ "@joeattardi/emoji-button@^2.12.1":
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/@joeattardi/emoji-button/-/emoji-button-2.12.1.tgz#190df7c00721e04742ed6f8852db828798a4cf98"
+ integrity sha512-rUuCXIcv4mRFK2IUKarYJN6J667wtH234smb1aQILzRf3/ycOoa6yUwnnvjxZeXMsPhuTnz15ndMOP2DhO5nNw==
+ dependencies:
+ "@fortawesome/fontawesome-svg-core" "^1.2.22"
+ "@fortawesome/free-regular-svg-icons" "^5.10.2"
+ "@fortawesome/free-solid-svg-icons" "^5.10.2"
+ "@popperjs/core" "^2.0.0"
+ focus-trap "^5.1.0"
+ tiny-emitter "^2.1.0"
+ tslib "^1.10.0"
+ twemoji "^12.1.5"
+
+ "@popperjs/core@^2.0.0":
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.2.3.tgz#0ae22b5650ab0b8fe508047245b66e71fc59e983"
+ integrity sha512-68EQPzEZRrpFavFX40V2+80eqzQIhgza2AGTXW+i8laxSA4It+Y13rmZInrAYoIujp8YO7YJPbvgOesDZcIulQ==
+
+ "@popperjs/core@^2.2.0":
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.3.2.tgz#1e56eb99bccddbda6a3e29aa4f3660f5b23edc43"
+ integrity sha512-18Tz3QghwsuHUC4gTNoxcEw1ClsrJ+lRypYpm+aucQonYNnmskQYvDZZKLHMPvQ7OwthWJl715UEX+Tg2fJkJw==
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
dependencies:
"@types/sizzle" "*"
- "@types/js-cookie@^2.2.5":
- version "2.2.5"
- resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.5.tgz#38dfaacae8623b37cc0b0d27398e574e3fc28b1e"
- integrity sha512-cpmwBRcHJmmZx0OGU7aPVwGWGbs4iKwVYchk9iuMtxNCA2zorwdaTz4GkLgs2WGxiRZRFKnV1k6tRUHX7tBMxg==
+ "@types/js-cookie@^2.2.6":
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f"
+ integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw==
"@types/json-schema@^7.0.3":
version "7.0.4"
dependencies:
"@types/markdown-it" "*"
-"@types/markdown-it@*":
+"@types/markdown-it@*", "@types/markdown-it@^0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.9.tgz#a5d552f95216c478e0a27a5acc1b28dcffd989ce"
integrity sha512-IFSepyZXbF4dgSvsk8EsgaQ/8Msv1I5eTL0BZ0X3iGO9jw6tCVtPG8HchIPm3wrkmGdqZOD42kE0zplVi1gYDA==
dependencies:
"@types/linkify-it" "*"
- "@types/node@^13.9.2":
- version "13.9.2"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349"
- integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg==
-"@types/markdown-it@^10.0.0":
- version "10.0.0"
- resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-10.0.0.tgz#a2b5f9fb444bb27c1e0c4a0116fea09b3c6ebc1e"
- integrity sha512-7UPBg1W0rfsqQ1JwNFfhxibKO0t7Q0scNt96XcFIFLGE/vhZamzZayaFS2LKha/26Pz7b/2GgiaxQZ1GUwW0dA==
- dependencies:
- "@types/linkify-it" "*"
- "@types/mdurl" "*"
-
-"@types/mdurl@*":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
- integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
-
+ "@types/node@^13.11.1":
+ version "13.11.1"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"
+ integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+ chalk@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72"
+ integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
chardet@^0.4.0:
version "0.4.2"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
isobject "^3.0.0"
static-extend "^0.1.1"
- classcat@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/classcat/-/classcat-1.1.3.tgz#ec748eecd962ec195a5d8f73f01d67c3d9040912"
- integrity sha512-nuf6HJ5RlEgUUPqN/giIy1wsfA0LJwCHpo/aMGMwEIAxYypbLW/ZdPH4SNrF+OwdrkL3wxJmAs4GPyoE3ZkQ4w==
+ classcat@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/classcat/-/classcat-4.0.2.tgz#bd5d51b656e01e9cdd21c1aae3d29ed035a52126"
+ integrity sha512-RlMPOPp8VDu3CJOUVorPumhz/CI+t9ft6f0uexxxCguk28/M+Kf27eQXjNWeDTisEQWei/30oDfITOQqr1TNpQ==
clean-css@^4.1.9:
version "4.2.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83"
integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==
- compare-versions@^3.5.1:
- version "3.5.1"
- resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393"
- integrity sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==
+ compare-versions@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
+ integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
component-emitter@^1.2.1:
version "1.3.0"
object.values "^1.1.0"
resolve "^1.12.0"
- eslint-plugin-jane@^7.2.0:
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jane/-/eslint-plugin-jane-7.2.0.tgz#a2454a6700c644e6c86821ca294adf303e75eddc"
- integrity sha512-/BPZrfxWX9T45gJSf4/2GHfBYgsBYTW7StAQfxL8PxWABZIQKWPWy/5ZokX7UaJlgKHAoC42rJHCQLK5hmfJNA==
+ eslint-plugin-jane@^7.2.1:
+ version "7.2.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jane/-/eslint-plugin-jane-7.2.1.tgz#5ffba9ce75e0a5e5dbe3918fc0c5332d2cd89c13"
+ integrity sha512-hUmhEkHTDq6lQ4oLWZV5cLut9L67fcTiy0USbTsEOx658i9Jdikedt8NJhtamRqO5OUHBGSPU0JkOqBtVNUD+A==
dependencies:
"@typescript-eslint/eslint-plugin" "2.24.0"
"@typescript-eslint/parser" "2.24.0"
eslint-plugin-prettier "3.1.2"
eslint-plugin-promise "4.2.1"
eslint-plugin-react "7.19.0"
- eslint-plugin-react-hooks "2.5.0"
+ eslint-plugin-react-hooks "2.5.1"
eslint-plugin-unicorn "17.2.0"
eslint-plugin-jest@23.8.2:
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
- eslint-plugin-react-hooks@2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.0.tgz#c50ab7ca5945ce6d1cf8248d9e185c80b54171b6"
- integrity sha512-bzvdX47Jx847bgAYf0FPX3u1oxU+mKU8tqrpj4UX9A96SbAmj/HVEefEy6rJUog5u8QIlOPTKZcBpGn5kkKfAQ==
+ eslint-plugin-react-hooks@2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.1.tgz#4ef5930592588ce171abeb26f400c7fbcbc23cd0"
+ integrity sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g==
eslint-plugin-react@7.19.0:
version "7.19.0"
dependencies:
chain-able "^1.0.1"
+ focus-trap@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-5.1.0.tgz#64a0bfabd95c382103397dbc96bfef3a3cf8e5ad"
+ integrity sha512-CkB/nrO55069QAUjWFBpX6oc+9V90Qhgpe6fBWApzruMq5gnlh90Oo7iSSDK7pKiV5ugG6OY2AXM5mxcmL3lwQ==
+ dependencies:
+ tabbable "^4.0.0"
+ xtend "^4.0.1"
+
for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
- husky@^4.2.3:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.3.tgz#3b18d2ee5febe99e27f2983500202daffbc3151e"
- integrity sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==
+ husky@^4.2.5:
+ version "4.2.5"
+ resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
+ integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==
dependencies:
- chalk "^3.0.0"
+ chalk "^4.0.0"
ci-info "^2.0.0"
- compare-versions "^3.5.1"
+ compare-versions "^3.6.0"
cosmiconfig "^6.0.0"
find-versions "^3.2.0"
opencollective-postinstall "^2.0.2"
slash "^3.0.0"
which-pm-runs "^1.0.0"
- i18next@^19.3.3:
- version "19.3.3"
- resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.3.3.tgz#04bd79b315e5fe2c87ab8f411e5d55eda0a17bd8"
- integrity sha512-CnuPqep5/JsltkGvQqzYN4d79eCe0TreCBRF3a8qHHi8x4SON1qqZ/pvR2X7BfNkNqpA5HXIqw0E731H+VsgSg==
+ i18next@^19.4.1:
+ version "19.4.1"
+ resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.4.1.tgz#4929d15d3d01e4712350a368d005cefa50ff5455"
+ integrity sha512-dC3ue15jkLebN2je4xEjfjVYd/fSAo+UVK9f+JxvceCJRowkI+S0lGohgKejqU+FYLfvw9IAPylIIEWwR8Djrg==
dependencies:
"@babel/runtime" "^7.3.1"
dependencies:
uc.micro "^1.0.1"
- lint-staged@^10.0.8:
- version "10.0.8"
- resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.0.8.tgz#0f7849cdc336061f25f5d4fcbcfa385701ff4739"
- integrity sha512-Oa9eS4DJqvQMVdywXfEor6F4vP+21fPHF8LUXgBbVWUSWBddjqsvO6Bv1LwMChmgQZZqwUvgJSHlu8HFHAPZmA==
+ lint-staged@^10.1.3:
+ version "10.1.3"
+ resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.1.3.tgz#da27713d3ac519da305381b4de87d5f866b1d2f1"
+ integrity sha512-o2OkLxgVns5RwSC5QF7waeAjJA5nz5gnUfqL311LkZcFipKV7TztrSlhNUK5nQX9H0E5NELAdduMQ+M/JPT7RQ==
dependencies:
chalk "^3.0.0"
commander "^4.0.1"
dependencies:
fast-diff "^1.1.2"
- prettier@^1.18.2:
- version "1.19.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
- integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
+ prettier@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.4.tgz#2d1bae173e355996ee355ec9830a7a1ee05457ef"
+ integrity sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==
pretty-time@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
- rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.3:
+ rxjs@^6.3.3, rxjs@^6.5.3:
version "6.5.4"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
dependencies:
tslib "^1.9.0"
+ rxjs@^6.5.5:
+ version "6.5.5"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
+ integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
+ dependencies:
+ tslib "^1.9.0"
+
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
source-map-resolve "^0.5.0"
use "^3.1.0"
- sortpack@^2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/sortpack/-/sortpack-2.1.2.tgz#25bf86f2923c81f43a00a2166ff4d271fafeed11"
- integrity sha512-43fSND1vmAdyfgC38aOkVxZBV331f4blF8acjwQmx7Gba4nuL2ene/Cq5eixNmDhKA/qQHnvSeAl+jEWb31rfg==
+ sortpack@^2.1.4:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/sortpack/-/sortpack-2.1.4.tgz#a2e251c5868455135cc41d3c98a53756a6de5282"
+ integrity sha512-RGD0l9kGmuPelXMT8WMMiSv1MkUkaqElB39nMkboIaqVkYns1aaNx263B2EE5QzF1YVUOrBlXnQpd7RX68SSow==
source-map-resolve@^0.5.0:
version "0.5.3"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
+ tabbable@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261"
+ integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==
+
table@^5.2.3:
version "5.4.6"
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
slice-ansi "^2.1.0"
string-width "^3.0.0"
- terser@^4.6.7:
- version "4.6.7"
- resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72"
- integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==
+ terser@^4.6.11:
+ version "4.6.11"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f"
+ integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
+ tiny-emitter@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
+ integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
+
tiny-invariant@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
- tippy.js@^6.1.0:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.1.0.tgz#9c58b94f92f3044d5e861b9d83da3c2a6d3d4323"
- integrity sha512-cRFydlVZlvo4soQSUfVNbH2K77zDUhDAzaAjxseyn81gGIa+j72y98yDL2yB0n8gas/E+Zlr1iOyR5ckslUFqA==
+ tippy.js@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.1.1.tgz#9ed09aa4f9c47fb06a0e280e03055f898f5ddfff"
+ integrity sha512-Sk+FPihack9XFbPOc2jRbn6iRLA9my2a8qhaGY6wwD3EeW57/xY5PAPkZOutKVYDWLyNZ/laCkJqg7QJG/gqQw==
dependencies:
- "@popperjs/core" "^2.1.1"
+ "@popperjs/core" "^2.2.0"
tmp@^0.0.33:
version "0.0.33"
psl "^1.1.24"
punycode "^1.4.1"
- tributejs@^5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.2.tgz#d8492d974d3098d6016248d689fb063cda6e77f7"
- integrity sha512-R9ff/q6w4T5f3Y9+RL+qinog3X1eAj1UnR/yfZaGJ8D3wuJs4/vicrGYul9+fgS9EJ/iYgwARekTb92xwark0g==
+ tributejs@^5.1.3:
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.3.tgz#980600fc72865be5868893078b4bfde721129eae"
+ integrity sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==
- ts-node@^8.7.0:
- version "8.7.0"
- resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.7.0.tgz#266186947596bef9f3a034687595b30e31b20976"
- integrity sha512-s659CsHrsxaRVDEleuOkGvbsA0rWHtszUNEt1r0CgAFN5ZZTQtDzpsluS7W5pOGJIa1xZE8R/zK4dEs+ldFezg==
+ ts-node@^8.8.2:
+ version "8.8.2"
+ resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f"
+ integrity sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==
dependencies:
arg "^4.1.0"
diff "^4.0.1"
source-map-support "^0.5.6"
yn "3.1.1"
- ts-transform-classcat@^0.0.2:
- version "0.0.2"
- resolved "https://registry.yarnpkg.com/ts-transform-classcat/-/ts-transform-classcat-0.0.2.tgz#2386c9418f3a7c1f03261ff51225b70d0a7664fb"
- integrity sha512-7laOOhgVxWVqvhK10mIEfedJx2nnNOS8J4P/6a/ehXtHFvsBVRRS9/FcTifgzJweOScZsF5BRD5VOGeNidMSqQ==
- dependencies:
- typescript "^2.6.2"
+ ts-transform-classcat@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/ts-transform-classcat/-/ts-transform-classcat-1.0.0.tgz#6ae1be1b32f1f3c6b1c4232daf8a28e3ced0b62f"
+ integrity sha512-LWXEYvBwHDOqBBtoDWSUmbPMsw8FI9vD4XZm98RgziN9UCIj5MRtpmXuP5YYoimCTlPU+D4TFR3IqS+5xSzWsQ==
- ts-transform-inferno@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/ts-transform-inferno/-/ts-transform-inferno-4.0.2.tgz#06b9be45edf874ba7a6ebfb6107ba782509c6afe"
- integrity sha512-CZb4+w/2l2zikPZ/c51fi3n+qnR2HCEfAS73oGQB80aqRLffkZqm25kYYTMmqUW2+oVfs4M5AZa0z14cvxlQ5w==
+ ts-transform-inferno@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/ts-transform-inferno/-/ts-transform-inferno-4.0.3.tgz#2cc0eb125abdaff24b8298106a618ab7c6319edc"
+ integrity sha512-Pcg0PVQwJ7Fpv4+3R9obFNsrNKQyLbmUqsjeG7T7r4/4UTgIl0MSwurexjtuGpCp2iv2X/i9ffKPAfAOyYJ9og==
+
+ tslib@^1.10.0:
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
+ integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-12.1.3.tgz#916c0153e77bd5f1011e7a99cbeacf52e43c9371"
integrity sha512-ND4LZXF4X92/PFrzSgGkq6KPPg8swy/U0yRw1k/+izWRVmq1HYi3khPwV3XIB6FRudgVICAaBhJfW8e8G3HC7Q==
- twemoji@^12.1.2:
+ twemoji@^12.1.2, twemoji@^12.1.5:
version "12.1.5"
resolved "https://registry.yarnpkg.com/twemoji/-/twemoji-12.1.5.tgz#a961fb65a1afcb1f729ad7e59391f9fe969820b9"
integrity sha512-B0PBVy5xomwb1M/WZxf/IqPZfnoIYy1skXnlHjMwLwTNfZ9ljh8VgWQktAPcJXu8080WoEh6YwQGPVhDVqvrVQ==
media-typer "0.3.0"
mime-types "~2.1.24"
- typescript@^2.6.2:
- version "2.9.2"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
- integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
-
typescript@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
dependencies:
"@babel/runtime-corejs3" "^7.8.3"
+ xtend@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
+ integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+
yaml@^1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.7.2.tgz#f26aabf738590ab61efaca502358e48dc9f348b2"