Initial
Nix build doesn't work, blocked on https://github.com/kolloch/crate2nix/issues/207
This commit is contained in:
commit
ca1cdc8179
43 changed files with 5126 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
2495
Cargo.lock
generated
Normal file
2495
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "shitter"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = "3.0.0-beta.2"
|
||||||
|
parking_lot = "0.11.1"
|
||||||
|
reqwest = { version = "0.11.4", default-features = false, features = ["gzip", "json", "rustls-tls"] }
|
||||||
|
rocket = "0.5.0-rc.1"
|
||||||
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
|
serde_json = "1.0.64"
|
||||||
|
stderrlog = "0.5.1"
|
||||||
|
thiserror = "1.0.26"
|
||||||
|
tokio = { version = "1.9.0", features = ["full"] }
|
||||||
|
typed-html = { git = "https://github.com/bodil/typed-html", rev = "d95ce1a2930f2385d4f3765d061dbeff3503107f" }
|
||||||
|
url = "2.2.2"
|
4
README.md
Normal file
4
README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
shitter: Twitter Client
|
||||||
|
===
|
||||||
|
|
||||||
|
License: AGPL
|
12
default.nix
Normal file
12
default.nix
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
(import
|
||||||
|
(let
|
||||||
|
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||||
|
in
|
||||||
|
fetchTarball {
|
||||||
|
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||||
|
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
src = ./.;
|
||||||
|
}).defaultNix
|
125
flake.lock
Normal file
125
flake.lock
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"crate2nix": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1627923438,
|
||||||
|
"narHash": "sha256-/O/FDJynVXOdFhSXQGU2uH/FQF3MS93WMyeY+9fcRaU=",
|
||||||
|
"owner": "kolloch",
|
||||||
|
"repo": "crate2nix",
|
||||||
|
"rev": "29460b5c411defa5e8e0851fe7ecbca0f0fa41d3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "kolloch",
|
||||||
|
"repo": "crate2nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1627913399,
|
||||||
|
"narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1614513358,
|
||||||
|
"narHash": "sha256-LakhOx3S1dRjnh0b5Dg3mbZyH0ToC9I8Y2wKSkBaTzU=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "5466c5bbece17adaab2d82fae80b46e807611bf3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1627978428,
|
||||||
|
"narHash": "sha256-813SF+K9wEwHVTOhgVpaC3CSZWcjap7Pv3bWppV0U44=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "b3ca5f904aa0f3341413f14e3bd8303a6acd39de",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1617325113,
|
||||||
|
"narHash": "sha256-GksR0nvGxfZ79T91UUtWjjccxazv6Yh/MvEJ82v1Xmw=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "54c1e44240d8a527a8f4892608c4bce5440c3ecb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"crate2nix": "crate2nix",
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay",
|
||||||
|
"utils": "utils"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1627957145,
|
||||||
|
"narHash": "sha256-cY5lS2S/RMsC1xFtkcmhLXlVP7ahZoxFeKedkXDvIzY=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "ab6f3086de97980e4fdcb0560921852a407e0b79",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1623875721,
|
||||||
|
"narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "f7e004a55b120c02ecb6219596820fcd32ca8772",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
43
flake.nix
Normal file
43
flake.nix
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
description = "shitter";
|
||||||
|
inputs = {
|
||||||
|
utils.url = "github:numtide/flake-utils";
|
||||||
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
|
crate2nix = { url = "github:kolloch/crate2nix"; flake = false; };
|
||||||
|
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, utils, rust-overlay, crate2nix, ... }:
|
||||||
|
let
|
||||||
|
name = "shitter";
|
||||||
|
rustChannel = "stable";
|
||||||
|
in
|
||||||
|
utils.lib.eachDefaultSystem
|
||||||
|
(system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [
|
||||||
|
rust-overlay.overlay
|
||||||
|
(self: super: {
|
||||||
|
rustc = self.rust-bin.${rustChannel}.latest.default;
|
||||||
|
cargo = self.rust-bin.${rustChannel}.latest.default;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
inherit (import "${crate2nix}/tools.nix" { inherit pkgs; }) generatedCargoNix;
|
||||||
|
project = pkgs.callPackage
|
||||||
|
(generatedCargoNix { inherit name; src = ./.; })
|
||||||
|
{ defaultCrateOverrides = pkgs.defaultCrateOverrides; };
|
||||||
|
buildInputs = with pkgs; [];
|
||||||
|
in rec {
|
||||||
|
packages.${name} = project.rootCrate.build;
|
||||||
|
defaultPackage = packages.${name};
|
||||||
|
apps.${name} = utils.lib.mkApp {
|
||||||
|
inherit name;
|
||||||
|
drv = packages.${name};
|
||||||
|
};
|
||||||
|
defaultApp = apps.${name};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
39
sass/general.scss
Normal file
39
sass/general.scss
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
.panel-container {
|
||||||
|
margin: auto;
|
||||||
|
font-size: 130%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-panel {
|
||||||
|
@include center-panel(var(--error_red));
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar > form {
|
||||||
|
@include center-panel(var(--darkest_grey));
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: var(--bg_elements);
|
||||||
|
color: var(--fg_color);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--bg_elements);
|
||||||
|
color: var(--fg_color);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
margin-right: 8px;
|
||||||
|
height: unset;
|
||||||
|
}
|
||||||
|
}
|
97
sass/include/_mixins.css
Normal file
97
sass/include/_mixins.css
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
@import '_variables';
|
||||||
|
|
||||||
|
@mixin panel($width, $max-width) {
|
||||||
|
max-width: $max-width;
|
||||||
|
margin: 0 auto;
|
||||||
|
float: none;
|
||||||
|
border-radius: 0;
|
||||||
|
position: relative;
|
||||||
|
width: $width;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin play-button {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.overlay-circle {
|
||||||
|
border-color: var(--play_button_hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-triangle {
|
||||||
|
border-color: transparent transparent transparent var(--play_button_hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin breakable {
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin ellipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin center-panel($bg) {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
background: $bg;
|
||||||
|
box-shadow: 0 0 15px $shadow_dark;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin input-colors {
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
border-color: var(--accent_light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin search-resize($width, $rows) {
|
||||||
|
@media(max-width: $width) {
|
||||||
|
.search-toggles {
|
||||||
|
grid-template-columns: repeat($rows, auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-panel-toggle:checked ~ .search-panel {
|
||||||
|
@if $rows == 6 {
|
||||||
|
max-height: 200px !important;
|
||||||
|
}
|
||||||
|
@if $rows == 5 {
|
||||||
|
max-height: 300px !important;
|
||||||
|
}
|
||||||
|
@if $rows == 4 {
|
||||||
|
max-height: 300px !important;
|
||||||
|
}
|
||||||
|
@if $rows == 3 {
|
||||||
|
max-height: 365px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin create-toggle($elem, $height) {
|
||||||
|
##{$elem}-toggle {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&:checked ~ .#{$elem} {
|
||||||
|
max-height: $height;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked ~ label .icon-down:before {
|
||||||
|
transform: rotate(180deg) translateY(-1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
sass/include/_variables.scss
Normal file
44
sass/include/_variables.scss
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// colors
|
||||||
|
$bg_color: #0F0F0F;
|
||||||
|
$fg_color: #F8F8F2;
|
||||||
|
$fg_faded: #F8F8F2CF;
|
||||||
|
$fg_dark: #FF6C60;
|
||||||
|
$fg_nav: #FF6C60;
|
||||||
|
|
||||||
|
$bg_panel: #161616;
|
||||||
|
$bg_elements: #121212;
|
||||||
|
$bg_overlays: #1F1F1F;
|
||||||
|
$bg_hover: #1A1A1A;
|
||||||
|
|
||||||
|
$grey: #888889;
|
||||||
|
$dark_grey: #404040;
|
||||||
|
$darker_grey: #282828;
|
||||||
|
$darkest_grey: #222222;
|
||||||
|
$border_grey: #3E3E35;
|
||||||
|
|
||||||
|
$accent: #FF6C60;
|
||||||
|
$accent_light: #FFACA0;
|
||||||
|
$accent_dark: #8A3731;
|
||||||
|
$accent_border: #FF6C6091;
|
||||||
|
|
||||||
|
$play_button: #D8574D;
|
||||||
|
$play_button_hover: #FF6C60;
|
||||||
|
|
||||||
|
$more_replies_dots: #AD433B;
|
||||||
|
$error_red: #420A05;
|
||||||
|
|
||||||
|
$verified_blue: #1DA1F2;
|
||||||
|
$icon_text: $fg_color;
|
||||||
|
|
||||||
|
$tab: $fg_color;
|
||||||
|
$tab_selected: $accent;
|
||||||
|
|
||||||
|
$shadow: rgba(0,0,0,.6);
|
||||||
|
$shadow_dark: rgba(0,0,0,.2);
|
||||||
|
|
||||||
|
//fonts
|
||||||
|
$font_0: Helvetica Neue;
|
||||||
|
$font_1: Helvetica;
|
||||||
|
$font_2: Arial;
|
||||||
|
$font_3: sans-serif;
|
||||||
|
$font_4: fontello;
|
164
sass/index.scss
Normal file
164
sass/index.scss
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
@import '_variables';
|
||||||
|
|
||||||
|
@import 'tweet/_base';
|
||||||
|
@import 'profile/_base';
|
||||||
|
@import 'general';
|
||||||
|
@import 'navbar';
|
||||||
|
@import 'inputs';
|
||||||
|
@import 'timeline';
|
||||||
|
@import 'search';
|
||||||
|
|
||||||
|
body {
|
||||||
|
// colors
|
||||||
|
--bg_color: #{$bg_color};
|
||||||
|
--fg_color: #{$fg_color};
|
||||||
|
--fg_faded: #{$fg_faded};
|
||||||
|
--fg_dark: #{$fg_dark};
|
||||||
|
--fg_nav: #{$fg_nav};
|
||||||
|
|
||||||
|
--bg_panel: #{$bg_panel};
|
||||||
|
--bg_elements: #{$bg_elements};
|
||||||
|
--bg_overlays: #{$bg_overlays};
|
||||||
|
--bg_hover: #{$bg_hover};
|
||||||
|
|
||||||
|
--grey: #{$grey};
|
||||||
|
--dark_grey: #{$dark_grey};
|
||||||
|
--darker_grey: #{$darker_grey};
|
||||||
|
--darkest_grey: #{$darkest_grey};
|
||||||
|
--border_grey: #{$border_grey};
|
||||||
|
|
||||||
|
--accent: #{$accent};
|
||||||
|
--accent_light: #{$accent_light};
|
||||||
|
--accent_dark: #{$accent_dark};
|
||||||
|
--accent_border: #{$accent_border};
|
||||||
|
|
||||||
|
--play_button: #{$play_button};
|
||||||
|
--play_button_hover: #{$play_button_hover};
|
||||||
|
|
||||||
|
--more_replies_dots: #{$more_replies_dots};
|
||||||
|
--error_red: #{$error_red};
|
||||||
|
|
||||||
|
--verified_blue: #{$verified_blue};
|
||||||
|
--icon_text: #{$icon_text};
|
||||||
|
|
||||||
|
--tab: #{$fg_color};
|
||||||
|
--tab_selected: #{$accent};
|
||||||
|
|
||||||
|
--profile_stat: #{$fg_color};
|
||||||
|
|
||||||
|
background-color: var(--bg_color);
|
||||||
|
color: var(--fg_color);
|
||||||
|
font-family: $font_0, $font_1, $font_2, $font_3;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
outline: unset;
|
||||||
|
margin: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 14px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--accent);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: -0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
width: 100%;
|
||||||
|
padding: .6em 0 .3em 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
border-bottom: 1px solid var(--border_grey);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-note {
|
||||||
|
border-top: 1px solid var(--border_grey);
|
||||||
|
border-bottom: 1px solid var(--border_grey);
|
||||||
|
padding: 6px 0 8px 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 50px;
|
||||||
|
margin: auto;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-panel {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 10px;
|
||||||
|
background-color: var(--bg_overlays);
|
||||||
|
padding: 10px 15px;
|
||||||
|
align-self: start;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verified-icon {
|
||||||
|
color: var(--icon_text);
|
||||||
|
background-color: var(--verified_blue);
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 2px 0 3px 3px;
|
||||||
|
padding-top: 2px;
|
||||||
|
height: 12px;
|
||||||
|
width: 14px;
|
||||||
|
font-size: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width: 600px) {
|
||||||
|
.preferences-container {
|
||||||
|
max-width: 95vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item, .nav-item .icon-container {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
185
sass/inputs.scss
Normal file
185
sass/inputs.scss
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
button {
|
||||||
|
@include input-colors;
|
||||||
|
background-color: var(--bg_elements);
|
||||||
|
color: var(--fg_color);
|
||||||
|
border: 1px solid var(--accent_border);
|
||||||
|
padding: 3px 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="date"],
|
||||||
|
select {
|
||||||
|
@include input-colors;
|
||||||
|
background-color: var(--bg_elements);
|
||||||
|
padding: 1px 4px;
|
||||||
|
color: var(--fg_color);
|
||||||
|
border: 1px solid var(--accent_border);
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 2px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="date"]::-webkit-inner-spin-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="date"]::-webkit-clear-button {
|
||||||
|
margin-left: 17px;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
filter: hue-rotate(120deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
input::-webkit-calendar-picker-indicator {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::-webkit-datetime-edit-day-field:focus,
|
||||||
|
input::-webkit-datetime-edit-month-field:focus,
|
||||||
|
input::-webkit-datetime-edit-year-field:focus {
|
||||||
|
background-color: var(--accent);
|
||||||
|
color: var(--fg_color);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range {
|
||||||
|
.date-input {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-title {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button button {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
float: none;
|
||||||
|
padding: unset;
|
||||||
|
padding-left: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent_light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
right: 0;
|
||||||
|
height: 17px;
|
||||||
|
width: 17px;
|
||||||
|
background-color: var(--bg_elements);
|
||||||
|
border: 1px solid var(--accent_border);
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
padding-right: 22px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
|
||||||
|
&:checked ~ .checkbox:after {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover input ~ .checkbox {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active input ~ .checkbox {
|
||||||
|
border-color: var(--accent_light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox:after {
|
||||||
|
left: 2px;
|
||||||
|
bottom: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: $font_4;
|
||||||
|
content: '\e803';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pref-group {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preferences {
|
||||||
|
button {
|
||||||
|
margin: 6px 0 3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
padding-right: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
display: block;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
max-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pref-group {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pref-input {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pref-reset {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
}
|
88
sass/navbar.scss
Normal file
88
sass/navbar.scss
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
@import '_variables';
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed;
|
||||||
|
background-color: var(--bg_overlays);
|
||||||
|
box-shadow: 0 0 4px $shadow;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
z-index: 1000;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
a, .icon-button button {
|
||||||
|
color: var(--fg_nav);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-nav {
|
||||||
|
margin: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-basis: 920px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent_light);
|
||||||
|
text-decoration: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo {
|
||||||
|
display: block;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
line-height: 50px;
|
||||||
|
height: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
text-align: right;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right a {
|
||||||
|
padding-left: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent_light);
|
||||||
|
text-decoration: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lp {
|
||||||
|
height: 14px;
|
||||||
|
margin-top: 2px;
|
||||||
|
display: block;
|
||||||
|
fill: var(--fg_nav);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
fill: var(--accent_light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-info:before {
|
||||||
|
margin: 0 -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-cog {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
73
sass/profile/_base.scss
Normal file
73
sass/profile/_base.scss
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
@import 'card';
|
||||||
|
@import 'photo-rail';
|
||||||
|
|
||||||
|
.profile-tabs {
|
||||||
|
@include panel(auto, 900px);
|
||||||
|
|
||||||
|
.timeline-container {
|
||||||
|
float: right;
|
||||||
|
width: 68% !important;
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-banner {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inherit;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-banner-color {
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-tab {
|
||||||
|
padding: 0 4px 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
max-width: 32%;
|
||||||
|
top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-result .username {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-result .tweet-header {
|
||||||
|
margin-bottom: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width: 600px) {
|
||||||
|
.profile-tabs {
|
||||||
|
width: 100vw;
|
||||||
|
|
||||||
|
.timeline-container {
|
||||||
|
width: 100% !important;
|
||||||
|
|
||||||
|
.tab-item wide {
|
||||||
|
flex-grow: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-tab {
|
||||||
|
width: 100%;
|
||||||
|
max-width: unset;
|
||||||
|
position: initial !important;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
124
sass/profile/card.scss
Normal file
124
sass/profile/card.scss
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
.profile-card {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
background: var(--bg_panel);
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-info {
|
||||||
|
@include breakable;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-tabs-name {
|
||||||
|
@include breakable;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-username {
|
||||||
|
@include breakable;
|
||||||
|
color: var(--fg_color);
|
||||||
|
font-size: 14px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-fullname {
|
||||||
|
@include breakable;
|
||||||
|
color: var(--fg_color);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: none;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-avatar {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
margin-right: 4px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
border: 4px solid var(--darker_grey);
|
||||||
|
background: var(--bg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-extra {
|
||||||
|
display: contents;
|
||||||
|
flex: 100%;
|
||||||
|
margin-top: 7px;
|
||||||
|
|
||||||
|
.profile-bio {
|
||||||
|
@include breakable;
|
||||||
|
width: 100%;
|
||||||
|
margin: 4px -6px 6px 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-joindate, .profile-location, profile-website {
|
||||||
|
color: var(--fg_faded);
|
||||||
|
margin: 2px 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-extra-links {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-statlist {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: table-cell;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stat-header {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--profile_stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stat-num {
|
||||||
|
display: block;
|
||||||
|
color: var(--profile_stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width: 600px) {
|
||||||
|
.profile-card-info {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-tabs-name {
|
||||||
|
@include breakable;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-avatar {
|
||||||
|
height: 60px;
|
||||||
|
width: unset;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-width: 2px;
|
||||||
|
width: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
sass/profile/photo-rail.scss
Normal file
97
sass/profile/photo-rail.scss
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
@import '_variables';
|
||||||
|
|
||||||
|
.photo-rail {
|
||||||
|
&-card {
|
||||||
|
float: left;
|
||||||
|
background: var(--bg_panel);
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
padding: 5px 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header-mobile {
|
||||||
|
display: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 5px 12px 0;
|
||||||
|
width: 100%;
|
||||||
|
float: unset;
|
||||||
|
color: var(--accent);
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-gap: 3px 3px;
|
||||||
|
padding: 5px 12px 12px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
padding-top: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: cover;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include create-toggle(photo-rail-grid, 640px);
|
||||||
|
#photo-rail-grid-toggle:checked ~ .photo-rail-grid {
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width: 600px) {
|
||||||
|
.photo-rail-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-rail-header-mobile {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-rail-grid {
|
||||||
|
max-height: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.4s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width: 600px) {
|
||||||
|
.photo-rail-grid {
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#photo-rail-grid-toggle:checked ~ .photo-rail-grid {
|
||||||
|
max-height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width: 450px) {
|
||||||
|
.photo-rail-grid {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#photo-rail-grid-toggle:checked ~ .photo-rail-grid {
|
||||||
|
max-height: 450px;
|
||||||
|
}
|
||||||
|
}
|
120
sass/search.scss
Normal file
120
sass/search.scss
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
.search-title {
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-field {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0 2px 0 0;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pref-input {
|
||||||
|
margin: 0 4px 0 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
height: calc(100% - 4px);
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
> label {
|
||||||
|
display: inline;
|
||||||
|
background-color: var(--bg_elements);
|
||||||
|
color: var(--fg_color);
|
||||||
|
border: 1px solid var(--accent_border);
|
||||||
|
padding: 1px 6px 2px 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
|
||||||
|
@include input-colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include create-toggle(search-panel, 200px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-panel {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.4s;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
font-weight: initial;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
line-height: 1.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container {
|
||||||
|
display: inline;
|
||||||
|
padding-right: unset;
|
||||||
|
margin-bottom: unset;
|
||||||
|
margin-left: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
right: unset;
|
||||||
|
left: -22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container .checkbox:after {
|
||||||
|
top: -4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
line-height: unset;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
height: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pref-input {
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
height: 21px;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-toggles {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, auto);
|
||||||
|
grid-column-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-tabs {
|
||||||
|
@include search-resize(820px, 5);
|
||||||
|
@include search-resize(725px, 4);
|
||||||
|
@include search-resize(600px, 6);
|
||||||
|
@include search-resize(560px, 5);
|
||||||
|
@include search-resize(480px, 4);
|
||||||
|
@include search-resize(410px, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include search-resize(560px, 5);
|
||||||
|
@include search-resize(480px, 4);
|
||||||
|
@include search-resize(410px, 3);
|
161
sass/timeline.scss
Normal file
161
sass/timeline.scss
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
@import '_variables';
|
||||||
|
|
||||||
|
.timeline-container {
|
||||||
|
@include panel(100%, 600px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
background-color: var(--bg_panel);
|
||||||
|
|
||||||
|
> div:not(:first-child) {
|
||||||
|
border-top: 1px solid var(--border_grey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-header {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--bg_panel);
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px;
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
button {
|
||||||
|
float: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-banner img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-description {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
background-color: var(--bg_panel);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
flex: 1 1 0;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
border-bottom: .1rem solid transparent;
|
||||||
|
color: var(--tab);
|
||||||
|
display: block;
|
||||||
|
padding: 8px 0;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-bottom-color: var(--tab_selected);
|
||||||
|
color: var(--tab_selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active a {
|
||||||
|
border-bottom-color: var(--tab_selected);
|
||||||
|
color: var(--tab_selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.wide {
|
||||||
|
flex-grow: 1.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-footer {
|
||||||
|
background-color: var(--bg_panel);
|
||||||
|
padding: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-protected {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-none {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-end {
|
||||||
|
background-color: var(--bg_panel);
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-more {
|
||||||
|
background-color: var(--bg_panel);
|
||||||
|
text-align: center;
|
||||||
|
padding: .75em 0;
|
||||||
|
display: block !important;
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: var(--darkest_grey);
|
||||||
|
display: inline-block;
|
||||||
|
height: 2em;
|
||||||
|
padding: 0 2em;
|
||||||
|
line-height: 2em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--darker_grey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-ref {
|
||||||
|
background-color: var(--bg_color);
|
||||||
|
border-top: none !important;
|
||||||
|
|
||||||
|
.icon-down {
|
||||||
|
font-size: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent_light);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
transform: rotate(180deg) translateY(-1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
border-left-width: 0;
|
||||||
|
min-width: 0;
|
||||||
|
padding: .75em;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
205
sass/tweet/_base.scss
Normal file
205
sass/tweet/_base.scss
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
@import 'thread';
|
||||||
|
@import 'media';
|
||||||
|
@import 'video';
|
||||||
|
@import 'embed';
|
||||||
|
@import 'card';
|
||||||
|
@import 'poll';
|
||||||
|
@import 'quote';
|
||||||
|
|
||||||
|
.tweet-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 58px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-content {
|
||||||
|
font-family: $font_3;
|
||||||
|
line-height: 1.3em;
|
||||||
|
pointer-events: all;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-bidi {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-header {
|
||||||
|
padding: 0;
|
||||||
|
vertical-align: bottom;
|
||||||
|
flex-basis: 100%;
|
||||||
|
margin-bottom: .2em;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
word-break: break-all;
|
||||||
|
max-width: 100%;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-name-row {
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullname-and-username {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullname {
|
||||||
|
@include ellipsis;
|
||||||
|
flex-shrink: 2;
|
||||||
|
max-width: 80%;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--fg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
@include ellipsis;
|
||||||
|
min-width: 1.6em;
|
||||||
|
margin-left: .4em;
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-date {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-date a, .username, .show-more a {
|
||||||
|
color: var(--fg_dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-published {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: var(--grey);
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-avatar {
|
||||||
|
display: contents !important;
|
||||||
|
|
||||||
|
img {
|
||||||
|
float: left;
|
||||||
|
margin-top: 3px;
|
||||||
|
margin-left: -58px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar.mini {
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-top: -1px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribution {
|
||||||
|
display: flex;
|
||||||
|
pointer-events: all;
|
||||||
|
margin: 5px 0;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: var(--fg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-tag-block {
|
||||||
|
padding-top: 5px;
|
||||||
|
pointer-events: all;
|
||||||
|
color: var(--fg_faded);
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-tag, .icon-container {
|
||||||
|
color: var(--fg_faded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-container .media-tag-block {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-geo {
|
||||||
|
color: var(--fg_faded);
|
||||||
|
}
|
||||||
|
|
||||||
|
.replying-to {
|
||||||
|
color: var(--fg_faded);
|
||||||
|
margin: -2px 0 4px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.retweet-header, .pinned, .tweet-stats {
|
||||||
|
align-content: center;
|
||||||
|
color: var(--grey);
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 22px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
@include ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.retweet-header {
|
||||||
|
margin-top: -5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-stats {
|
||||||
|
margin-bottom: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-stat {
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-thread {
|
||||||
|
display: block;
|
||||||
|
pointer-events: all;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailable-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: solid 1px var(--dark_grey);
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-link {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg_hover);
|
||||||
|
}
|
||||||
|
}
|
118
sass/tweet/card.scss
Normal file
118
sass/tweet/card.scss
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin: 5px 0;
|
||||||
|
pointer-events: all;
|
||||||
|
max-height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-container {
|
||||||
|
border-radius: 10px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--dark_grey);
|
||||||
|
background-color: var(--bg_elements);
|
||||||
|
overflow: hidden;
|
||||||
|
color: inherit;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
text-decoration: none !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachments {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
@include ellipsis;
|
||||||
|
white-space: unset;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-description {
|
||||||
|
margin: 0.3em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-destination {
|
||||||
|
@include ellipsis;
|
||||||
|
color: var(--grey);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content-container {
|
||||||
|
color: unset;
|
||||||
|
overflow: auto;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image-container {
|
||||||
|
width: 98px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
padding-top: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: var(--bg_overlays);
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 400px;
|
||||||
|
display: block;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-overlay {
|
||||||
|
@include play-button;
|
||||||
|
opacity: 0.8;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.large {
|
||||||
|
.card-container {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image-container {
|
||||||
|
width: unset;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
position: unset;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--dark_grey);
|
||||||
|
border-width: 0;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
17
sass/tweet/embed.scss
Normal file
17
sass/tweet/embed.scss
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
.embed-video {
|
||||||
|
.gallery-video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
background-color: black;
|
||||||
|
top: 0%;
|
||||||
|
left: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container {
|
||||||
|
max-height: unset;
|
||||||
|
}
|
||||||
|
}
|
119
sass/tweet/media.scss
Normal file
119
sass/tweet/media.scss
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
@import '_variables';
|
||||||
|
|
||||||
|
.gallery-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-grow: 1;
|
||||||
|
max-height: 379.5px;
|
||||||
|
max-width: 533px;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
.still-image {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachments {
|
||||||
|
margin-top: .35em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 600px;
|
||||||
|
border-radius: 7px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-flow: column;
|
||||||
|
background-color: var(--bg_color);
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
.image-attachment {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment {
|
||||||
|
position: relative;
|
||||||
|
line-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 .25em 0 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 2em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin: 0;
|
||||||
|
max-height: 530px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-gif video {
|
||||||
|
max-height: 530px;
|
||||||
|
background-color: #101010;
|
||||||
|
}
|
||||||
|
|
||||||
|
.still-image {
|
||||||
|
max-height: 379.5px;
|
||||||
|
max-width: 533px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: cover;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 379.5px;
|
||||||
|
flex-basis: 300px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .single-image {
|
||||||
|
// display: inline-block;
|
||||||
|
// width: 100%;
|
||||||
|
// max-height: 600px;
|
||||||
|
|
||||||
|
// .attachments {
|
||||||
|
// width: unset;
|
||||||
|
// max-height: unset;
|
||||||
|
// display: inherit;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
.overlay-circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--dark_grey);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
border-width: 5px;
|
||||||
|
border-color: var(--play_button);
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-triangle {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 12px 0 12px 17px;
|
||||||
|
border-color: transparent transparent transparent var(--play_button);
|
||||||
|
margin-left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-gif {
|
||||||
|
display: table;
|
||||||
|
background-color: unset;
|
||||||
|
width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
42
sass/tweet/poll.scss
Normal file
42
sass/tweet/poll.scss
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
@import '_variables';
|
||||||
|
|
||||||
|
.poll-meter {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
margin: 6px 0;
|
||||||
|
height: 26px;
|
||||||
|
background: var(--bg_color);
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-choice-bar {
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
background: var(--dark_grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-choice-value {
|
||||||
|
position: relative;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 6px;
|
||||||
|
min-width: 30px;
|
||||||
|
text-align: right;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-choice-option {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-info {
|
||||||
|
color: var(--grey);
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leader .poll-choice-bar {
|
||||||
|
background: var(--accent_dark);
|
||||||
|
}
|
94
sass/tweet/quote.scss
Normal file
94
sass/tweet/quote.scss
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
@import '_variables';
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
margin-top: 10px;
|
||||||
|
border: solid 1px var(--dark_grey);
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--bg_elements);
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: all;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unavailable:hover {
|
||||||
|
border-color: var(--dark_grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tweet-name-row {
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-text {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
padding: 0px 8px 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-thread {
|
||||||
|
padding: 0px 8px 6px 8px;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replying-to {
|
||||||
|
padding: 0px 8px;
|
||||||
|
margin: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailable-quote {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-link {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-media-container {
|
||||||
|
max-height: 300px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachments {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-gif {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-gif .attachment {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--bg_color);
|
||||||
|
|
||||||
|
video {
|
||||||
|
height: unset;
|
||||||
|
width: unset;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-video, .gallery-gif {
|
||||||
|
max-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.still-image img {
|
||||||
|
max-height: 250px
|
||||||
|
}
|
||||||
|
}
|
113
sass/tweet/thread.scss
Normal file
113
sass/tweet/thread.scss
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
.conversation {
|
||||||
|
@include panel(100%, 600px);
|
||||||
|
|
||||||
|
.show-more {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-thread {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background-color: var(--bg_panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-tweet, .replies {
|
||||||
|
padding-top: 50px;
|
||||||
|
margin-top: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-tweet .tweet-content {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media(max-width: 600px) {
|
||||||
|
.main-tweet .tweet-content {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply {
|
||||||
|
background-color: var(--bg_panel);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-line {
|
||||||
|
.timeline-item::before,
|
||||||
|
&.timeline-item::before {
|
||||||
|
background: var(--accent_dark);
|
||||||
|
content: '';
|
||||||
|
position: relative;
|
||||||
|
min-width: 3px;
|
||||||
|
width: 3px;
|
||||||
|
left: 26px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-left: -3px;
|
||||||
|
margin-bottom: 37px;
|
||||||
|
top: 56px;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.with-header:not(:first-child)::after {
|
||||||
|
background: var(--accent_dark);
|
||||||
|
content: '';
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
min-width: 3px;
|
||||||
|
width: 3px;
|
||||||
|
right: calc(100% - 26px);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-left: -3px;
|
||||||
|
margin-bottom: 37px;
|
||||||
|
bottom: 10px;
|
||||||
|
height: 30px;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailable::before {
|
||||||
|
top: 48px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-replies::before {
|
||||||
|
content: '...';
|
||||||
|
background: unset;
|
||||||
|
color: var(--more_replies_dots);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 0.25em;
|
||||||
|
left: 1.2em;
|
||||||
|
width: 5px;
|
||||||
|
top: 2px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-left: -2.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.earlier-replies {
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: -5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item.thread-last::before {
|
||||||
|
background: unset;
|
||||||
|
min-width: unset;
|
||||||
|
width: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-replies {
|
||||||
|
padding-top: 0.3em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-replies-text {
|
||||||
|
@include ellipsis;
|
||||||
|
display: block;
|
||||||
|
margin-left: 58px;
|
||||||
|
padding: 7px 0;
|
||||||
|
}
|
63
sass/tweet/video.scss
Normal file
63
sass/tweet/video.scss
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
video {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-video {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-video.card-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container {
|
||||||
|
max-height: 530px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-overlay {
|
||||||
|
@include play-button;
|
||||||
|
background-color: $shadow;
|
||||||
|
|
||||||
|
p {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
text-align: center;
|
||||||
|
top: calc(50% - 20px);
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
top: calc(50% - 20px);
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 5px 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
2
scripts/generate_scss.sh
Normal file
2
scripts/generate_scss.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/bash
|
||||||
|
sassc -Isass/include sass/index.scss static/css/style.css
|
70
src/api/mod.rs
Normal file
70
src/api/mod.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
pub mod profile;
|
||||||
|
pub mod timeline;
|
||||||
|
mod token;
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use reqwest::Url;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::consts::*;
|
||||||
|
use crate::errors::Result;
|
||||||
|
|
||||||
|
use self::token::Token;
|
||||||
|
|
||||||
|
pub struct Api {
|
||||||
|
client: reqwest::Client,
|
||||||
|
token: RwLock<Option<Token>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Api {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
let client = reqwest::ClientBuilder::new()
|
||||||
|
.gzip(true)
|
||||||
|
.use_rustls_tls()
|
||||||
|
.timeout(Duration::new(15, 0))
|
||||||
|
.referer(false)
|
||||||
|
.build()?;
|
||||||
|
let token = RwLock::new(None);
|
||||||
|
Ok(Api { client, token })
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch<T>(&self, url: Url) -> Result<T>
|
||||||
|
where
|
||||||
|
T: for<'de> Deserialize<'de> + Debug,
|
||||||
|
{
|
||||||
|
let token = self.get_token().await?;
|
||||||
|
|
||||||
|
println!("fetching from {}", url);
|
||||||
|
|
||||||
|
use std::{fs::File, io::Write};
|
||||||
|
let mut file = File::create(format!("fetched-{}.json", url.as_str().replace("/", "-")))?;
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.client
|
||||||
|
.get(url)
|
||||||
|
.header("Connection", "keep-alive")
|
||||||
|
.header("Authorization", AUTH)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("X-Guest-Token", &token.token)
|
||||||
|
.header("X-Twitter-Active-User", "yes")
|
||||||
|
.header("Authority", "api.twitter.com")
|
||||||
|
.header("Accept-Language", "en-US,en;q=0.9")
|
||||||
|
.header("Accept", "*/*")
|
||||||
|
.header("DNT", "1")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
let text = resp.text().await?;
|
||||||
|
println!("text: {:?}", text);
|
||||||
|
|
||||||
|
file.write(text.as_bytes())?;
|
||||||
|
std::mem::drop(file);
|
||||||
|
|
||||||
|
let body: T = serde_json::from_str(&text)?;
|
||||||
|
println!("body: {:?}", body);
|
||||||
|
|
||||||
|
Ok(body)
|
||||||
|
}
|
||||||
|
}
|
38
src/api/profile.rs
Normal file
38
src/api/profile.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use reqwest::Url;
|
||||||
|
|
||||||
|
use crate::consts::*;
|
||||||
|
use crate::errors::Result;
|
||||||
|
|
||||||
|
use super::Api;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Profile {
|
||||||
|
#[serde(rename = "id_str")]
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub screen_name: String,
|
||||||
|
pub location: String,
|
||||||
|
pub description: String,
|
||||||
|
pub url: String,
|
||||||
|
pub followers_count: usize,
|
||||||
|
pub friends_count: usize,
|
||||||
|
#[serde(rename = "favourites_count")]
|
||||||
|
pub likes_count: usize,
|
||||||
|
#[serde(rename = "statuses_count")]
|
||||||
|
pub tweets_count: usize,
|
||||||
|
pub media_count: usize,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub protected: bool,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub verified: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Api {
|
||||||
|
pub async fn fetch_profile(&self, username: impl AsRef<str>) -> Result<Profile> {
|
||||||
|
let username = username.as_ref();
|
||||||
|
let url = Url::parse_with_params(USER_SHOW_URL, &[("screen_name", username)])?;
|
||||||
|
|
||||||
|
let profile = self.fetch::<Profile>(url).await?;
|
||||||
|
Ok(profile)
|
||||||
|
}
|
||||||
|
}
|
58
src/api/timeline.rs
Normal file
58
src/api/timeline.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use reqwest::Url;
|
||||||
|
|
||||||
|
use crate::consts::*;
|
||||||
|
use crate::errors::Result;
|
||||||
|
|
||||||
|
use super::profile::Profile;
|
||||||
|
use super::Api;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Timeline {
|
||||||
|
#[serde(rename = "globalObjects")]
|
||||||
|
pub global_objects: GlobalObjects,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct GlobalObjects {
|
||||||
|
pub tweets: HashMap<String, Tweet>,
|
||||||
|
pub users: HashMap<String, Profile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Tweet {
|
||||||
|
#[serde(rename = "id_str")]
|
||||||
|
pub id: String,
|
||||||
|
pub created_at: String,
|
||||||
|
pub text: String,
|
||||||
|
pub source: String,
|
||||||
|
#[serde(rename = "user_id_str")]
|
||||||
|
pub user_id: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub is_quote_status: bool,
|
||||||
|
#[serde(rename = "quoted_status_id_str")]
|
||||||
|
pub quoted_status_id: Option<String>,
|
||||||
|
pub retweet_count: usize,
|
||||||
|
#[serde(rename = "favorite_count")]
|
||||||
|
pub likes_count: usize,
|
||||||
|
#[serde(rename = "conversation_id_str")]
|
||||||
|
pub conversation_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Api {
|
||||||
|
pub async fn fetch_timeline(&self, id: impl AsRef<str>, replies: bool) -> Result<Timeline> {
|
||||||
|
let id = id.as_ref();
|
||||||
|
let replies = match replies {
|
||||||
|
true => "1",
|
||||||
|
false => "0",
|
||||||
|
};
|
||||||
|
let url = Url::parse_with_params(
|
||||||
|
&format!("{}/{}.json", TIMELINE_STUB, id),
|
||||||
|
&[("userId", id), ("include_tweet_replies", replies)],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let timeline = self.fetch::<Timeline>(url).await?;
|
||||||
|
Ok(timeline)
|
||||||
|
}
|
||||||
|
}
|
63
src/api/token.rs
Normal file
63
src/api/token.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crate::consts::*;
|
||||||
|
use crate::errors::Result;
|
||||||
|
|
||||||
|
use super::Api;
|
||||||
|
|
||||||
|
pub const RESET_PERIOD: u64 = 15 * 60;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Token {
|
||||||
|
pub token: String,
|
||||||
|
reset: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Api {
|
||||||
|
pub async fn get_token(&self) -> Result<Token> {
|
||||||
|
if let Some(current_token) = &*self.token.read().await {
|
||||||
|
let now = Instant::now();
|
||||||
|
if current_token.reset > now {
|
||||||
|
return Ok(current_token.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = self.fetch_token().await?;
|
||||||
|
*self.token.write().await = Some(token.clone());
|
||||||
|
return Ok(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_token(&self) -> Result<Token> {
|
||||||
|
let resp = self
|
||||||
|
.client
|
||||||
|
.post(ACTIVATE_URL)
|
||||||
|
.header(
|
||||||
|
"Accept",
|
||||||
|
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||||
|
)
|
||||||
|
.header("Accept-Language", "en-US,en;q=0.5")
|
||||||
|
.header("User-Agent", generate_user_agent())
|
||||||
|
.header("Authorization", AUTH)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Response {
|
||||||
|
guest_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let response: Response = resp.json().await?;
|
||||||
|
let now = Instant::now();
|
||||||
|
let reset = now + Duration::new(RESET_PERIOD, 0);
|
||||||
|
let token = Token {
|
||||||
|
token: response.guest_token,
|
||||||
|
reset,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_user_agent() -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
10
src/config.rs
Normal file
10
src/config.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
bind_addr: String,
|
||||||
|
port: u16,
|
||||||
|
http_max_connections: usize,
|
||||||
|
|
||||||
|
title: String,
|
||||||
|
hostname: String,
|
||||||
|
static_dir: String,
|
||||||
|
}
|
6
src/consts.rs
Normal file
6
src/consts.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// TODO: no idea how this was obtained, will check back with twitter developer site later
|
||||||
|
pub const AUTH: &str = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA";
|
||||||
|
|
||||||
|
pub const ACTIVATE_URL: &str = "https://api.twitter.com/1.1/guest/activate.json";
|
||||||
|
pub const USER_SHOW_URL: &str = "https://api.twitter.com/1.1/users/show.json";
|
||||||
|
pub const TIMELINE_STUB: &str = "https://api.twitter.com/2/timeline/profile";
|
32
src/errors.rs
Normal file
32
src/errors.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use rocket::{
|
||||||
|
http::Status,
|
||||||
|
request::Request,
|
||||||
|
response::{self, Responder, Response},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("rate limit exceeded")]
|
||||||
|
RateLimitExceeded,
|
||||||
|
|
||||||
|
#[error("reqwest error: {0}")]
|
||||||
|
Reqwest(#[from] reqwest::Error),
|
||||||
|
|
||||||
|
#[error("url parse error: {0}")]
|
||||||
|
UrlParse(#[from] url::ParseError),
|
||||||
|
|
||||||
|
#[error("serde_json error: {0}")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("generic io error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> Responder<'r, 'static> for Error {
|
||||||
|
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
|
||||||
|
error!("Error: {:?}", self);
|
||||||
|
Response::build().status(Status::InternalServerError).ok()
|
||||||
|
}
|
||||||
|
}
|
57
src/main.rs
Normal file
57
src/main.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![recursion_limit = "512"]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate thiserror;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate typed_html;
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
mod config;
|
||||||
|
mod consts;
|
||||||
|
mod errors;
|
||||||
|
mod render;
|
||||||
|
mod routes;
|
||||||
|
mod views;
|
||||||
|
|
||||||
|
use clap::Clap;
|
||||||
|
use rocket::fs::{FileServer, relative};
|
||||||
|
|
||||||
|
use crate::api::Api;
|
||||||
|
|
||||||
|
#[derive(Clap)]
|
||||||
|
struct Opt {
|
||||||
|
#[clap(short = 'v', long = "verbose", parse(from_occurrences))]
|
||||||
|
verbose: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/favicon.ico")]
|
||||||
|
fn favicon() {}
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
let opt = Opt::parse();
|
||||||
|
// stderrlog::new()
|
||||||
|
// .module(module_path!())
|
||||||
|
// .verbosity(opt.verbose)
|
||||||
|
// .init()
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
use routes::timeline;
|
||||||
|
let api = Api::new().unwrap();
|
||||||
|
rocket::build()
|
||||||
|
.mount("/static", FileServer::from(relative!("/static")))
|
||||||
|
.mount(
|
||||||
|
"/",
|
||||||
|
routes![
|
||||||
|
favicon,
|
||||||
|
timeline::fetch_single_timeline,
|
||||||
|
timeline::fetch_single_timeline_tab,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.manage(api)
|
||||||
|
}
|
1
src/render.rs
Normal file
1
src/render.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub fn render_main() {}
|
1
src/routes/mod.rs
Normal file
1
src/routes/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod timeline;
|
68
src/routes/timeline.rs
Normal file
68
src/routes/timeline.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use rocket::{request::FromParam, State, response::content};
|
||||||
|
use typed_html::dom::DOMTree;
|
||||||
|
|
||||||
|
use crate::api::Api;
|
||||||
|
use crate::errors::Result;
|
||||||
|
use crate::views::timeline as view;
|
||||||
|
|
||||||
|
pub type FetchSingleTimelineReturn = Result<content::Html<String>>;
|
||||||
|
|
||||||
|
#[get("/<name>")]
|
||||||
|
pub async fn fetch_single_timeline(api: &State<Api>, name: String) -> FetchSingleTimelineReturn {
|
||||||
|
fetch_single_timeline_private(api, name, Tab::Main).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/<name>/<tab>")]
|
||||||
|
pub async fn fetch_single_timeline_tab(
|
||||||
|
api: &State<Api>,
|
||||||
|
name: String,
|
||||||
|
tab: Tab,
|
||||||
|
) -> FetchSingleTimelineReturn {
|
||||||
|
fetch_single_timeline_private(api, name, tab).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_single_timeline_private(
|
||||||
|
api: &State<Api>,
|
||||||
|
name: String,
|
||||||
|
_tab: Tab,
|
||||||
|
) -> FetchSingleTimelineReturn {
|
||||||
|
let profile = api.fetch_profile(name).await?;
|
||||||
|
|
||||||
|
let timeline = api.fetch_timeline(profile.id, false).await?;
|
||||||
|
println!("timeline: {:?}", timeline);
|
||||||
|
|
||||||
|
let rendered: DOMTree<String> = html!(
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>"shitter"</title>
|
||||||
|
<link rel="stylesheet" href="/static/css/style.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{ view::render_timeline_tweets(timeline.global_objects.tweets.values()) }
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(content::Html(rendered.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Tab {
|
||||||
|
Main,
|
||||||
|
WithReplies,
|
||||||
|
Media,
|
||||||
|
Search,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromParam<'a> for Tab {
|
||||||
|
type Error = ();
|
||||||
|
fn from_param(string: &'a str) -> Result<Self, Self::Error> {
|
||||||
|
match string.to_lowercase().as_str() {
|
||||||
|
"" => Ok(Tab::Main),
|
||||||
|
"with_replies" => Ok(Tab::WithReplies),
|
||||||
|
"media" => Ok(Tab::Media),
|
||||||
|
"search" => Ok(Tab::Search),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
src/tokens.rs
Normal file
3
src/tokens.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub fn get_token() {
|
||||||
|
|
||||||
|
}
|
19
src/views/mod.rs
Normal file
19
src/views/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
pub mod timeline;
|
||||||
|
pub mod tweet;
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
use typed_html::{
|
||||||
|
dom::DOMTree,
|
||||||
|
types::{Class, SpacedSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn icon(icon: &str) -> DOMTree<String> {
|
||||||
|
let class_name = format!("icon-{}", icon);
|
||||||
|
let class_set: SpacedSet<Class> = [class_name.as_str()].try_into().unwrap();
|
||||||
|
html! (
|
||||||
|
<div class="icon-container">
|
||||||
|
<span class={ class_set }></span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
24
src/views/timeline.rs
Normal file
24
src/views/timeline.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use typed_html::{dom::DOMTree, elements::FlowContent};
|
||||||
|
|
||||||
|
use crate::api::timeline::Tweet;
|
||||||
|
|
||||||
|
use super::tweet::render_tweet;
|
||||||
|
|
||||||
|
pub fn render_to_top() -> Box<dyn FlowContent<String>> {
|
||||||
|
html!(
|
||||||
|
<div class="top-ref">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_timeline_tweets<'a>(tweets: impl Iterator<Item = &'a Tweet>) -> Box<dyn FlowContent<String>> {
|
||||||
|
let tweets_rendered = tweets.map(|tweet| render_tweet(tweet));
|
||||||
|
|
||||||
|
html!(
|
||||||
|
<div class="timeline">
|
||||||
|
{ tweets_rendered }
|
||||||
|
{ render_to_top() }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
13
src/views/tweet.rs
Normal file
13
src/views/tweet.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use typed_html::elements::FlowContent;
|
||||||
|
|
||||||
|
use crate::api::timeline::Tweet;
|
||||||
|
|
||||||
|
pub fn render_tweet(tweet: &Tweet) -> Box<dyn FlowContent<String>> {
|
||||||
|
html!(
|
||||||
|
<div class="timeline-item">
|
||||||
|
<div class="tweet-body">
|
||||||
|
{ text!(&tweet.text) }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
static/css/.gitignore
vendored
Normal file
1
static/css/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
style.css
|
Loading…
Reference in a new issue