aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/views')
-rw-r--r--FrontEnd/src/views/about/about.sass4
-rw-r--r--FrontEnd/src/views/about/index.css4
-rw-r--r--FrontEnd/src/views/about/index.tsx2
-rw-r--r--FrontEnd/src/views/admin/Admin.tsx2
-rw-r--r--FrontEnd/src/views/admin/admin.sass22
-rw-r--r--FrontEnd/src/views/admin/index.css19
-rw-r--r--FrontEnd/src/views/center/TimelineBoard.tsx17
-rw-r--r--FrontEnd/src/views/center/center.sass36
-rw-r--r--FrontEnd/src/views/center/index.css73
-rw-r--r--FrontEnd/src/views/center/index.tsx2
-rw-r--r--FrontEnd/src/views/common/AppBar.tsx2
-rw-r--r--FrontEnd/src/views/common/FlatButton.tsx36
-rw-r--r--FrontEnd/src/views/common/button/FlatButton.css45
-rw-r--r--FrontEnd/src/views/common/button/FlatButton.tsx36
-rw-r--r--FrontEnd/src/views/common/common.sass191
-rw-r--r--FrontEnd/src/views/common/index.css273
-rw-r--r--FrontEnd/src/views/home/home.sass29
-rw-r--r--FrontEnd/src/views/home/index.css73
-rw-r--r--FrontEnd/src/views/home/index.tsx2
-rw-r--r--FrontEnd/src/views/login/index.css3
-rw-r--r--FrontEnd/src/views/login/index.tsx2
-rw-r--r--FrontEnd/src/views/login/login.sass2
-rw-r--r--FrontEnd/src/views/search/index.css15
-rw-r--r--FrontEnd/src/views/search/index.tsx2
-rw-r--r--FrontEnd/src/views/search/search.sass13
-rw-r--r--FrontEnd/src/views/settings/index.css24
-rw-r--r--FrontEnd/src/views/settings/index.tsx2
-rw-r--r--FrontEnd/src/views/settings/settings.sass14
-rw-r--r--FrontEnd/src/views/timeline-common/Timeline.tsx4
-rw-r--r--FrontEnd/src/views/timeline-common/index.css289
-rw-r--r--FrontEnd/src/views/timeline-common/timeline-common.sass259
-rw-r--r--FrontEnd/src/views/timeline/timeline.sass0
-rw-r--r--FrontEnd/src/views/user/index.css9
-rw-r--r--FrontEnd/src/views/user/index.tsx2
-rw-r--r--FrontEnd/src/views/user/user.sass7
35 files changed, 891 insertions, 624 deletions
diff --git a/FrontEnd/src/views/about/about.sass b/FrontEnd/src/views/about/about.sass
deleted file mode 100644
index f4d00cae..00000000
--- a/FrontEnd/src/views/about/about.sass
+++ /dev/null
@@ -1,4 +0,0 @@
-.about-link-icon
- @extend .mx-2
- width: 1.2em
- height: 1.2em
diff --git a/FrontEnd/src/views/about/index.css b/FrontEnd/src/views/about/index.css
new file mode 100644
index 00000000..2574f4b7
--- /dev/null
+++ b/FrontEnd/src/views/about/index.css
@@ -0,0 +1,4 @@
+.about-link-icon {
+ width: 1.2em;
+ height: 1.2em;
+}
diff --git a/FrontEnd/src/views/about/index.tsx b/FrontEnd/src/views/about/index.tsx
index a8a53a97..db4814c4 100644
--- a/FrontEnd/src/views/about/index.tsx
+++ b/FrontEnd/src/views/about/index.tsx
@@ -4,6 +4,8 @@ import { useTranslation, Trans } from "react-i18next";
import authorAvatarUrl from "./author-avatar.png";
import githubLogoUrl from "./github.png";
+import "./index.css";
+
const frontendCredits: {
name: string;
url: string;
diff --git a/FrontEnd/src/views/admin/Admin.tsx b/FrontEnd/src/views/admin/Admin.tsx
index 0b6d1f05..34e7e2f6 100644
--- a/FrontEnd/src/views/admin/Admin.tsx
+++ b/FrontEnd/src/views/admin/Admin.tsx
@@ -9,6 +9,8 @@ import AdminNav from "./AdminNav";
import UserAdmin from "./UserAdmin";
import MoreAdmin from "./MoreAdmin";
+import "./index.css";
+
interface AdminProps {
user: AuthUser;
}
diff --git a/FrontEnd/src/views/admin/admin.sass b/FrontEnd/src/views/admin/admin.sass
deleted file mode 100644
index 1ce010f8..00000000
--- a/FrontEnd/src/views/admin/admin.sass
+++ /dev/null
@@ -1,22 +0,0 @@
-.admin-user-item
- position: relative
-
- .edit-mask
- position: absolute
- top: 0
- left: 0
- bottom: 0
- right: 0
-
- background: #ffffffc5
- position: absolute
-
- display: flex
- justify-content: center
- align-items: center
-
- @include media-breakpoint-down(xs)
- flex-direction: column
-
- button
- margin: 0.5em 2em
diff --git a/FrontEnd/src/views/admin/index.css b/FrontEnd/src/views/admin/index.css
new file mode 100644
index 00000000..00917600
--- /dev/null
+++ b/FrontEnd/src/views/admin/index.css
@@ -0,0 +1,19 @@
+.admin-user-item {
+ position: relative;
+}
+.admin-user-item .edit-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ background: #ffffffc5;
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+}
+.admin-user-item .edit-mask button {
+ margin: 0.5em 2em;
+}
diff --git a/FrontEnd/src/views/center/TimelineBoard.tsx b/FrontEnd/src/views/center/TimelineBoard.tsx
index 840c0415..a6a60b3d 100644
--- a/FrontEnd/src/views/center/TimelineBoard.tsx
+++ b/FrontEnd/src/views/center/TimelineBoard.tsx
@@ -9,6 +9,7 @@ import { HttpTimelineInfo } from "@/http/timeline";
import TimelineLogo from "../common/TimelineLogo";
import UserTimelineLogo from "../common/UserTimelineLogo";
import LoadFailReload from "../common/LoadFailReload";
+import FlatButton from "../common/button/FlatButton";
interface TimelineBoardItemProps {
timeline: HttpTimelineInfo;
@@ -231,23 +232,19 @@ const TimelineBoardUI: React.FC<TimelineBoardUIProps> = (props) => {
{title != null && <h3>{title}</h3>}
{editable &&
(editing ? (
- <div
- className="flat-button text-primary"
+ <FlatButton
+ text="done"
onClick={() => {
setEditing(false);
}}
- >
- {t("done")}
- </div>
+ />
) : (
- <div
- className="flat-button text-primary"
+ <FlatButton
+ text="edit"
onClick={() => {
setEditing(true);
}}
- >
- {t("edit")}
- </div>
+ />
))}
</div>
{(() => {
diff --git a/FrontEnd/src/views/center/center.sass b/FrontEnd/src/views/center/center.sass
deleted file mode 100644
index c0dfb9c0..00000000
--- a/FrontEnd/src/views/center/center.sass
+++ /dev/null
@@ -1,36 +0,0 @@
-.timeline-board
- @extend .cru-card
- @extend .d-flex
- @extend .flex-column
- @extend .py-3
- min-height: 200px
- height: 100%
- position: relative
-
-.timeline-board-header
- @extend .px-3
- display: flex
- align-items: center
- justify-content: space-between
-
-.timeline-board-item
- font-size: 1.1em
- @extend .px-3
- height: 48px
- transition: background 0.3s
- display: flex
- align-items: center
- .icon
- height: 1.3em
- color: black
- @extend .me-2
- &:hover
- background: $gray-300
- .right
- display: flex
- align-items: center
- flex-shrink: 0
- .title
- white-space: nowrap
- overflow: hidden
- text-overflow: ellipsis
diff --git a/FrontEnd/src/views/center/index.css b/FrontEnd/src/views/center/index.css
new file mode 100644
index 00000000..516aba52
--- /dev/null
+++ b/FrontEnd/src/views/center/index.css
@@ -0,0 +1,73 @@
+.timeline-board {
+ min-height: 200px;
+ height: 100%;
+ position: relative;
+}
+
+.timeline-board-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.timeline-board-item {
+ font-size: 1.1em;
+ height: 48px;
+ transition: background 0.3s;
+ display: flex;
+ align-items: center;
+}
+.timeline-board-item .icon {
+ height: 1.3em;
+ color: black;
+}
+.timeline-board-item:hover {
+ background: #dee2e6;
+}
+.timeline-board-item .right {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+}
+.timeline-board-item .title {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.home-timeline-list-item {
+ display: flex;
+ align-items: center;
+}
+
+.home-timeline-list-item-timeline {
+ transition: background 0.8s;
+ animation: 0.8s home-timeline-list-item-timeline-enter;
+}
+.home-timeline-list-item-timeline:hover {
+ background: #e9ecef;
+}
+
+@keyframes home-timeline-list-item-timeline-enter {
+ from {
+ transform: translate(-100%, 0);
+ opacity: 0;
+ }
+}
+.home-timeline-list-item-line {
+ width: 80px;
+ flex-shrink: 0;
+}
+
+@keyframes home-timeline-list-loading-head-animation {
+ from {
+ transform: translate(0, -30px);
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+.home-timeline-list-loading-head {
+ animation: 1s infinite home-timeline-list-loading-head-animation;
+}
diff --git a/FrontEnd/src/views/center/index.tsx b/FrontEnd/src/views/center/index.tsx
index 0a2abb2c..28d8b372 100644
--- a/FrontEnd/src/views/center/index.tsx
+++ b/FrontEnd/src/views/center/index.tsx
@@ -9,6 +9,8 @@ import SearchInput from "../common/SearchInput";
import CenterBoards from "./CenterBoards";
import TimelineCreateDialog from "./TimelineCreateDialog";
+import "./index.css";
+
const HomePage: React.FC = () => {
const history = useHistory();
diff --git a/FrontEnd/src/views/common/AppBar.tsx b/FrontEnd/src/views/common/AppBar.tsx
index 91dfbee9..ebc8bf0c 100644
--- a/FrontEnd/src/views/common/AppBar.tsx
+++ b/FrontEnd/src/views/common/AppBar.tsx
@@ -9,6 +9,8 @@ import { useUser } from "@/services/user";
import TimelineLogo from "./TimelineLogo";
import UserAvatar from "./user/UserAvatar";
+import "./index.css";
+
const AppBar: React.FC = (_) => {
const { t } = useTranslation();
diff --git a/FrontEnd/src/views/common/FlatButton.tsx b/FrontEnd/src/views/common/FlatButton.tsx
deleted file mode 100644
index b1f7a051..00000000
--- a/FrontEnd/src/views/common/FlatButton.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from "react";
-import classnames from "classnames";
-
-import { BootstrapThemeColor } from "@/common";
-
-export interface FlatButtonProps {
- variant?: BootstrapThemeColor | string;
- disabled?: boolean;
- className?: string;
- style?: React.CSSProperties;
- onClick?: () => void;
-}
-
-const FlatButton: React.FC<FlatButtonProps> = (props) => {
- const { disabled, className, style } = props;
- const variant = props.variant ?? "primary";
-
- const onClick = disabled ? undefined : props.onClick;
-
- return (
- <div
- className={classnames(
- "flat-button",
- variant,
- disabled ? "disabled" : null,
- className
- )}
- style={style}
- onClick={onClick}
- >
- {props.children}
- </div>
- );
-};
-
-export default FlatButton;
diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css
new file mode 100644
index 00000000..779e3562
--- /dev/null
+++ b/FrontEnd/src/views/common/button/FlatButton.css
@@ -0,0 +1,45 @@
+.cru-flat-button {
+ cursor: pointer;
+ padding: 0.2em 0.5em;
+ border-radius: 0.2em;
+}
+
+.cru-flat-button:hover:not(.disabled) {
+ background-color: #e9ecef;
+}
+
+.cru-flat-button.disabled {
+ cursor: default;
+}
+
+.cru-flat-button.primary {
+ color: var(--tl-primary-color);
+}
+
+.cru-flat-button.primary.disabled {
+ color: var(--tl-primary-lighter-color);
+}
+
+.cru-flat-button.secondary {
+ color: var(--tl-secondary-color);
+}
+
+.cru-flat-button.secondary.disabled {
+ color: var(--tl-secondary-lighter-color);
+}
+
+.cru-flat-button.success {
+ color: var(--tl-success-color);
+}
+
+.cru-flat-button.success.disabled {
+ color: var(--tl-success-lighter-color);
+}
+
+.cru-flat-button.danger {
+ color: var(--tl-danger-color);
+}
+
+.cru-flat-button.danger.disabled {
+ color: var(--tl-danger-ligher-color);
+}
diff --git a/FrontEnd/src/views/common/button/FlatButton.tsx b/FrontEnd/src/views/common/button/FlatButton.tsx
new file mode 100644
index 00000000..0727eb88
--- /dev/null
+++ b/FrontEnd/src/views/common/button/FlatButton.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+
+import { convertI18nText, I18nText } from "@/common";
+import { PaletteColorType } from "@/palette";
+
+import "./FlatButton.css";
+import classNames from "classnames";
+
+function _FlatButton(
+ {
+ text,
+ color,
+ onClick,
+ }: {
+ text: I18nText;
+ color?: PaletteColorType;
+ onClick?: () => void;
+ },
+ ref: React.ForwardedRef<HTMLButtonElement>
+): React.ReactElement | null {
+ const { t } = useTranslation();
+
+ return (
+ <button
+ ref={ref}
+ className={classNames("cru-flat-button", color ?? "primary")}
+ onClick={onClick}
+ >
+ {convertI18nText(text, t)}
+ </button>
+ );
+}
+
+const FlatButton = React.forwardRef(_FlatButton);
+export default FlatButton;
diff --git a/FrontEnd/src/views/common/common.sass b/FrontEnd/src/views/common/common.sass
deleted file mode 100644
index cbf7292e..00000000
--- a/FrontEnd/src/views/common/common.sass
+++ /dev/null
@@ -1,191 +0,0 @@
-.image-cropper-container
- position: relative
- box-sizing: border-box
- user-select: none
-
-.image-cropper-container img
- position: absolute
- left: 0
- top: 0
- width: 100%
- height: 100%
-
-.image-cropper-mask-container
- position: absolute
- left: 0
- top: 0
- right: 0
- bottom: 0
- overflow: hidden
-
-.image-cropper-mask
- position: absolute
- box-shadow: 0 0 0 10000px rgba(255, 255, 255, 80%)
- touch-action: none
-
-.image-cropper-handler
- position: absolute
- width: 26px
- height: 26px
- border: black solid 2px
- border-radius: 50%
- background: white
- touch-action: none
-
-.app-bar
- display: flex
- align-items: center
- height: 56px
-
- position: fixed
- z-index: 1030
- top: 0
- left: 0
- right: 0
-
- background-color: var(--tl-primary-color)
-
- transition: background-color 1s
-
- a
- color: var(--tl-text-on-primary-inactive-color)
- text-decoration: none
- margin: 0 1em
-
- &:hover
- color: var(--tl-text-on-primary-color)
-
- &.active
- color: var(--tl-text-on-primary-color)
-
-.app-bar-brand
- display: flex
- align-items: center
-
-.app-bar-brand-icon
- height: 2em
-
-.app-bar-main-area
- display: flex
- flex-grow: 1
-
-.app-bar-link-area
- display: flex
- align-items: center
- flex-shrink: 0
-
-.app-bar-user-area
- display: flex
- align-items: center
- flex-shrink: 0
- margin-left: auto
-
-.small-screen
- .app-bar-main-area
- position: absolute
- top: 56px
- left: 0
- right: 0
-
- transform-origin: top
- transition: transform 0.6s, background-color 1s
-
- background-color: var(--tl-primary-color)
-
- flex-direction: column
-
- &.app-bar-collapse
- transform: scale(1,0)
-
- a
- text-align: left
- padding: 0.5em 0.5em
-
- .app-bar-link-area
- flex-direction: column
- align-items: stretch
-
- .app-bar-user-area
- flex-direction: column
- align-items: stretch
- margin-left: unset
-
- .app-bar-avatar
- align-self: flex-end
-
-.app-bar-toggler
- margin-left: auto
- font-size: 2em
- margin-right: 1em
- color: var(--tl-text-on-primary-color)
- cursor: pointer
- user-select: none
-
-.cru-skeleton
- padding: 0 1em
-
-.cru-skeleton-line
- height: 1em
- background-color: #e6e6e6
- margin: 0.7em 0
- border-radius: 0.2em
-
- &.last
- width: 50%
-
-.cru-full-page
- position: fixed
- z-index: 1031
- left: 0
- top: 0
- right: 0
- bottom: 0
- background-color: white
- padding-top: 56px
-
-.cru-full-page-top-bar
- height: 56px
-
- position: absolute
- top: 0
- left: 0
- right: 0
- z-index: 1
-
- background-color: var(--tl-primary-color)
-
- display: flex
- align-items: center
-
-.cru-full-page-content-container
- overflow: scroll
-
-.cru-menu
- min-width: 200px
-
-.cru-menu-item
- font-size: 1.2em
- padding: 0.5em 1.5em
- cursor: pointer
-
- @each $color, $value in $theme-colors
- &.color-#{$color}
- color: $value
-
- &:hover
- color: white
- background-color: $value
-
-.cru-menu-item-icon
- margin-right: 1em
-
-.cru-menu-divider
- border-top: 1px solid $gray-200
-
-.cru-tab-pages-action-area
- display: flex
- align-items: center
-
-.cru-search-input
- display: flex
- flex-wrap: wrap
diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css
new file mode 100644
index 00000000..bfd82b58
--- /dev/null
+++ b/FrontEnd/src/views/common/index.css
@@ -0,0 +1,273 @@
+.image-cropper-container {
+ position: relative;
+ box-sizing: border-box;
+ user-select: none;
+}
+
+.image-cropper-container img {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.image-cropper-mask-container {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+}
+
+.image-cropper-mask {
+ position: absolute;
+ box-shadow: 0 0 0 10000px rgba(255, 255, 255, 0.8);
+ touch-action: none;
+}
+
+.image-cropper-handler {
+ position: absolute;
+ width: 26px;
+ height: 26px;
+ border: black solid 2px;
+ border-radius: 50%;
+ background: white;
+ touch-action: none;
+}
+
+.app-bar {
+ display: flex;
+ align-items: center;
+ height: 56px;
+ position: fixed;
+ z-index: 1030;
+ top: 0;
+ left: 0;
+ right: 0;
+ background-color: var(--tl-primary-color);
+ transition: background-color 1s;
+}
+.app-bar a {
+ color: var(--tl-text-on-primary-inactive-color);
+ text-decoration: none;
+ margin: 0 1em;
+}
+.app-bar a:hover {
+ color: var(--tl-text-on-primary-color);
+}
+.app-bar a.active {
+ color: var(--tl-text-on-primary-color);
+}
+
+.app-bar-brand {
+ display: flex;
+ align-items: center;
+}
+
+.app-bar-brand-icon {
+ height: 2em;
+}
+
+.app-bar-main-area {
+ display: flex;
+ flex-grow: 1;
+}
+
+.app-bar-link-area {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+}
+
+.app-bar-user-area {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ margin-left: auto;
+}
+
+.small-screen .app-bar-main-area {
+ position: absolute;
+ top: 56px;
+ left: 0;
+ right: 0;
+ transform-origin: top;
+ transition: transform 0.6s, background-color 1s;
+ background-color: var(--tl-primary-color);
+ flex-direction: column;
+}
+.small-screen .app-bar-main-area.app-bar-collapse {
+ transform: scale(1, 0);
+}
+.small-screen .app-bar-main-area a {
+ text-align: left;
+ padding: 0.5em 0.5em;
+}
+.small-screen .app-bar-link-area {
+ flex-direction: column;
+ align-items: stretch;
+}
+.small-screen .app-bar-user-area {
+ flex-direction: column;
+ align-items: stretch;
+ margin-left: unset;
+}
+.small-screen .app-bar-avatar {
+ align-self: flex-end;
+}
+
+.app-bar-toggler {
+ margin-left: auto;
+ font-size: 2em;
+ margin-right: 1em;
+ color: var(--tl-text-on-primary-color);
+ cursor: pointer;
+ user-select: none;
+}
+
+.cru-skeleton {
+ padding: 0 1em;
+}
+
+.cru-skeleton-line {
+ height: 1em;
+ background-color: #e6e6e6;
+ margin: 0.7em 0;
+ border-radius: 0.2em;
+}
+.cru-skeleton-line.last {
+ width: 50%;
+}
+
+.cru-full-page {
+ position: fixed;
+ z-index: 1031;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ background-color: white;
+ padding-top: 56px;
+}
+
+.cru-full-page-top-bar {
+ height: 56px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 1;
+ background-color: var(--tl-primary-color);
+ display: flex;
+ align-items: center;
+}
+
+.cru-full-page-content-container {
+ overflow: scroll;
+}
+
+.cru-menu {
+ min-width: 200px;
+}
+
+.cru-menu-item {
+ font-size: 1.2em;
+ padding: 0.5em 1.5em;
+ cursor: pointer;
+}
+.cru-menu-item.color-primary {
+ color: #0d6efd;
+}
+.cru-menu-item.color-primary:hover {
+ color: white;
+ background-color: #0d6efd;
+}
+.cru-menu-item.color-secondary {
+ color: #6c757d;
+}
+.cru-menu-item.color-secondary:hover {
+ color: white;
+ background-color: #6c757d;
+}
+.cru-menu-item.color-success {
+ color: #198754;
+}
+.cru-menu-item.color-success:hover {
+ color: white;
+ background-color: #198754;
+}
+.cru-menu-item.color-info {
+ color: #0dcaf0;
+}
+.cru-menu-item.color-info:hover {
+ color: white;
+ background-color: #0dcaf0;
+}
+.cru-menu-item.color-warning {
+ color: #ffc107;
+}
+.cru-menu-item.color-warning:hover {
+ color: white;
+ background-color: #ffc107;
+}
+.cru-menu-item.color-danger {
+ color: #dc3545;
+}
+.cru-menu-item.color-danger:hover {
+ color: white;
+ background-color: #dc3545;
+}
+.cru-menu-item.color-light {
+ color: #f8f9fa;
+}
+.cru-menu-item.color-light:hover {
+ color: white;
+ background-color: #f8f9fa;
+}
+.cru-menu-item.color-dark {
+ color: #212529;
+}
+.cru-menu-item.color-dark:hover {
+ color: white;
+ background-color: #212529;
+}
+
+.cru-menu-item-icon {
+ margin-right: 1em;
+}
+
+.cru-menu-divider {
+ border-top: 1px solid #e9ecef;
+}
+
+.cru-tab-pages-action-area {
+ display: flex;
+ align-items: center;
+}
+
+.cru-search-input {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.alert-container {
+ position: fixed;
+ z-index: 1070;
+}
+
+@media (min-width: 576px) {
+ .alert-container {
+ bottom: 0;
+ right: 0;
+ }
+}
+@media (max-width: 575.98px) {
+ .alert-container {
+ bottom: 0;
+ right: 0;
+ left: 0;
+ text-align: center;
+ }
+}
diff --git a/FrontEnd/src/views/home/home.sass b/FrontEnd/src/views/home/home.sass
deleted file mode 100644
index b4cda586..00000000
--- a/FrontEnd/src/views/home/home.sass
+++ /dev/null
@@ -1,29 +0,0 @@
-.home-timeline-list-item
- display: flex
- align-items: center
-
-.home-timeline-list-item-timeline
- transition: background 0.8s
- animation: 0.8s home-timeline-list-item-timeline-enter
- &:hover
- background: $gray-200
-
-@keyframes home-timeline-list-item-timeline-enter
- from
- transform: translate(-100%,0)
- opacity: 0
-
-.home-timeline-list-item-line
- width: 80px
- flex-shrink: 0
-
-@keyframes home-timeline-list-loading-head-animation
- from
- transform: translate(0,-30px)
- opacity: 1
-
- to
- opacity: 0
-
-.home-timeline-list-loading-head
- animation: 1s infinite home-timeline-list-loading-head-animation
diff --git a/FrontEnd/src/views/home/index.css b/FrontEnd/src/views/home/index.css
new file mode 100644
index 00000000..516aba52
--- /dev/null
+++ b/FrontEnd/src/views/home/index.css
@@ -0,0 +1,73 @@
+.timeline-board {
+ min-height: 200px;
+ height: 100%;
+ position: relative;
+}
+
+.timeline-board-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.timeline-board-item {
+ font-size: 1.1em;
+ height: 48px;
+ transition: background 0.3s;
+ display: flex;
+ align-items: center;
+}
+.timeline-board-item .icon {
+ height: 1.3em;
+ color: black;
+}
+.timeline-board-item:hover {
+ background: #dee2e6;
+}
+.timeline-board-item .right {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+}
+.timeline-board-item .title {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.home-timeline-list-item {
+ display: flex;
+ align-items: center;
+}
+
+.home-timeline-list-item-timeline {
+ transition: background 0.8s;
+ animation: 0.8s home-timeline-list-item-timeline-enter;
+}
+.home-timeline-list-item-timeline:hover {
+ background: #e9ecef;
+}
+
+@keyframes home-timeline-list-item-timeline-enter {
+ from {
+ transform: translate(-100%, 0);
+ opacity: 0;
+ }
+}
+.home-timeline-list-item-line {
+ width: 80px;
+ flex-shrink: 0;
+}
+
+@keyframes home-timeline-list-loading-head-animation {
+ from {
+ transform: translate(0, -30px);
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+.home-timeline-list-loading-head {
+ animation: 1s infinite home-timeline-list-loading-head-animation;
+}
diff --git a/FrontEnd/src/views/home/index.tsx b/FrontEnd/src/views/home/index.tsx
index 0eca23ee..ddb72e76 100644
--- a/FrontEnd/src/views/home/index.tsx
+++ b/FrontEnd/src/views/home/index.tsx
@@ -8,6 +8,8 @@ import SearchInput from "../common/SearchInput";
import TimelineListView from "./TimelineListView";
import WebsiteIntroduction from "./WebsiteIntroduction";
+import "./index.css";
+
const highlightTimelineMessageMap = {
loading: "home.loadingHighlightTimelines",
done: "home.loadedHighlightTimelines",
diff --git a/FrontEnd/src/views/login/index.css b/FrontEnd/src/views/login/index.css
new file mode 100644
index 00000000..dca7054d
--- /dev/null
+++ b/FrontEnd/src/views/login/index.css
@@ -0,0 +1,3 @@
+.login-container {
+ max-width: 600px;
+}
diff --git a/FrontEnd/src/views/login/index.tsx b/FrontEnd/src/views/login/index.tsx
index 6adcef39..a39a9972 100644
--- a/FrontEnd/src/views/login/index.tsx
+++ b/FrontEnd/src/views/login/index.tsx
@@ -8,6 +8,8 @@ import { useUser, userService } from "@/services/user";
import AppBar from "../common/AppBar";
import LoadingButton from "../common/LoadingButton";
+import "./index.css";
+
const LoginPage: React.FC = (_) => {
const { t } = useTranslation();
const history = useHistory();
diff --git a/FrontEnd/src/views/login/login.sass b/FrontEnd/src/views/login/login.sass
deleted file mode 100644
index 0bf385f5..00000000
--- a/FrontEnd/src/views/login/login.sass
+++ /dev/null
@@ -1,2 +0,0 @@
-.login-container
- max-width: 600px
diff --git a/FrontEnd/src/views/search/index.css b/FrontEnd/src/views/search/index.css
new file mode 100644
index 00000000..6ff4d9fa
--- /dev/null
+++ b/FrontEnd/src/views/search/index.css
@@ -0,0 +1,15 @@
+.timeline-search-result-item {
+ border: 1px solid;
+ border-color: #e9ecef;
+ background: #f8f9fa;
+ transition: all 0.3s;
+}
+.timeline-search-result-item:hover {
+ border-color: #0d6efd;
+}
+
+.timeline-search-result-item-avatar {
+ width: 2em;
+ height: 2em;
+ border-radius: 50%;
+}
diff --git a/FrontEnd/src/views/search/index.tsx b/FrontEnd/src/views/search/index.tsx
index 9a26802d..f5018c3e 100644
--- a/FrontEnd/src/views/search/index.tsx
+++ b/FrontEnd/src/views/search/index.tsx
@@ -11,6 +11,8 @@ import { HttpTimelineInfo } from "@/http/timeline";
import SearchInput from "../common/SearchInput";
import UserAvatar from "../common/user/UserAvatar";
+import "./index.css";
+
const TimelineSearchResultItemView: React.FC<{
timeline: HttpTimelineInfo;
}> = ({ timeline }) => {
diff --git a/FrontEnd/src/views/search/search.sass b/FrontEnd/src/views/search/search.sass
deleted file mode 100644
index 83f297fe..00000000
--- a/FrontEnd/src/views/search/search.sass
+++ /dev/null
@@ -1,13 +0,0 @@
-.timeline-search-result-item
- @extend .rounded
- border: 1px solid
- border-color: $gray-200
- background: $gray-100
- transition: all 0.3s
- &:hover
- border-color: $primary
-
-.timeline-search-result-item-avatar
- width: 2em
- height: 2em
- border-radius: 50%
diff --git a/FrontEnd/src/views/settings/index.css b/FrontEnd/src/views/settings/index.css
new file mode 100644
index 00000000..566d501b
--- /dev/null
+++ b/FrontEnd/src/views/settings/index.css
@@ -0,0 +1,24 @@
+.change-avatar-cropper-row {
+ max-height: 400px;
+}
+
+.change-avatar-img {
+ min-width: 50%;
+ max-width: 100%;
+ max-height: 400px;
+}
+
+.settings-item {
+ padding: 0.5em 1em;
+ transition: background 0.3s;
+ border-bottom: 1px solid #e9ecef;
+}
+.settings-item.first {
+ border-top: 1px solid #e9ecef;
+}
+.settings-item.clickable {
+ cursor: pointer;
+}
+.settings-item:hover {
+ background: #dee2e6;
+}
diff --git a/FrontEnd/src/views/settings/index.tsx b/FrontEnd/src/views/settings/index.tsx
index 04a2777a..f0bed222 100644
--- a/FrontEnd/src/views/settings/index.tsx
+++ b/FrontEnd/src/views/settings/index.tsx
@@ -9,6 +9,8 @@ import ChangePasswordDialog from "./ChangePasswordDialog";
import ChangeAvatarDialog from "./ChangeAvatarDialog";
import ChangeNicknameDialog from "./ChangeNicknameDialog";
+import "./index.css";
+
const ConfirmLogoutDialog: React.FC<{
onClose: () => void;
onConfirm: () => void;
diff --git a/FrontEnd/src/views/settings/settings.sass b/FrontEnd/src/views/settings/settings.sass
deleted file mode 100644
index 8c6d24b8..00000000
--- a/FrontEnd/src/views/settings/settings.sass
+++ /dev/null
@@ -1,14 +0,0 @@
-.settings-item
- padding: 0.5em 1em
- transition: background 0.3s
- border-bottom: 1px solid $gray-200
-
- &.first
- border-top: 1px solid $gray-200
-
- &.clickable
- cursor: pointer
-
- &:hover
- background: $gray-300
-
diff --git a/FrontEnd/src/views/timeline-common/Timeline.tsx b/FrontEnd/src/views/timeline-common/Timeline.tsx
index 31ea5870..21daa5e2 100644
--- a/FrontEnd/src/views/timeline-common/Timeline.tsx
+++ b/FrontEnd/src/views/timeline-common/Timeline.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { HubConnectionState } from "srcmicrosoft/signalr";
+import { HubConnectionState } from "@microsoft/signalr";
import {
HttpForbiddenError,
@@ -14,6 +14,8 @@ import TimelinePagedPostListView from "./TimelinePagedPostListView";
import TimelineTop from "./TimelineTop";
import TimelineLoading from "./TimelineLoading";
+import "./index.css";
+
export interface TimelineProps {
className?: string;
style?: React.CSSProperties;
diff --git a/FrontEnd/src/views/timeline-common/index.css b/FrontEnd/src/views/timeline-common/index.css
new file mode 100644
index 00000000..89399961
--- /dev/null
+++ b/FrontEnd/src/views/timeline-common/index.css
@@ -0,0 +1,289 @@
+.timeline {
+ z-index: 0;
+ position: relative;
+ width: 100%;
+ overflow-wrap: break-word;
+ animation: 1s timeline-enter;
+}
+
+@keyframes timeline-line-node-noncurrent {
+ to {
+ box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color);
+ }
+}
+@keyframes timeline-line-node-current {
+ to {
+ box-shadow: 0 0 20px 3px var(--tl-primary-enhance-lighter-color);
+ }
+}
+@keyframes timeline-line-node-loading {
+ to {
+ box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color);
+ }
+}
+@keyframes timeline-line-node-loading-edge {
+ from {
+ transform: rotate(0turn);
+ }
+ to {
+ transform: rotate(1turn);
+ }
+}
+@keyframes timeline-enter {
+ from {
+ transform: translate(0, -100vh);
+ }
+}
+@keyframes timeline-top-loading-enter {
+ from {
+ transform: translate(0, -100%);
+ }
+}
+@keyframes timeline-post-enter {
+ from {
+ transform: translate(0, -100%);
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+.timeline-top-loading-enter {
+ animation: 1s timeline-top-loading-enter;
+}
+
+.timeline-line {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 30px;
+ position: absolute;
+ z-index: 1;
+ left: 2em;
+ top: 0;
+ bottom: 0;
+ transition: left 0.5s;
+}
+@media (max-width: 575.98px) {
+ .timeline-line {
+ left: 1em;
+ }
+}
+.timeline-line .segment {
+ width: 7px;
+ background: var(--tl-primary-color);
+}
+.timeline-line .segment.start {
+ height: 1.8em;
+ flex: 0 0 auto;
+}
+.timeline-line .segment.end {
+ flex: 1 1 auto;
+}
+.timeline-line .segment.current-end {
+ height: 2em;
+ flex: 0 0 auto;
+ background: linear-gradient(var(--tl-primary-enhance-color), white);
+}
+.timeline-line .node-container {
+ flex: 0 0 auto;
+ position: relative;
+ width: 18px;
+ height: 18px;
+}
+.timeline-line .node {
+ width: 20px;
+ height: 20px;
+ position: absolute;
+ background: var(--tl-primary-color);
+ left: -1px;
+ top: -1px;
+ border-radius: 50%;
+ box-sizing: border-box;
+ z-index: 1;
+ animation: 1s infinite alternate;
+ animation-name: timeline-line-node-noncurrent;
+}
+.timeline-line .node-loading-edge {
+ color: var(--tl-primary-color);
+ width: 38px;
+ height: 38px;
+ position: absolute;
+ left: -10px;
+ top: -10px;
+ box-sizing: border-box;
+ z-index: 2;
+ animation: 1.5s linear infinite timeline-line-node-loading-edge;
+}
+.timeline-line.current .segment.start {
+ background: linear-gradient(
+ var(--tl-primary-color),
+ var(--tl-primary-enhance-color)
+ );
+}
+.timeline-line.current .segment.end {
+ background: var(--tl-primary-enhance-color);
+}
+.timeline-line.current .node {
+ background: var(--tl-primary-enhance-color);
+ animation-name: timeline-line-node-current;
+}
+.timeline-line.loading .node {
+ background: var(--tl-primary-color);
+ animation-name: timeline-line-node-loading;
+}
+
+.timeline-item.current {
+ padding-bottom: 2.5em;
+}
+
+.timeline-top {
+ position: relative;
+ text-align: right;
+}
+
+.timeline-item {
+ position: relative;
+ padding: 0.5em;
+}
+
+.timeline-item-card {
+ position: relative;
+ padding: 0.3em 0.5em 1em 4em;
+ transition: background 0.5s, padding-left 0.5s;
+ animation: 0.6s forwards;
+ opacity: 0;
+}
+@media (max-width: 575.98px) {
+ .timeline-item-card {
+ padding-left: 3em;
+ }
+}
+
+.timeline-item-header {
+ display: flex;
+ align-items: center;
+}
+
+.timeline-avatar {
+ border-radius: 50%;
+ width: 2em;
+ height: 2em;
+}
+
+.timeline-item-delete-button {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+}
+
+.timeline-content {
+ white-space: pre-line;
+}
+
+.timeline-content-image {
+ max-width: 80%;
+ max-height: 200px;
+}
+
+.timeline-date-item {
+ position: relative;
+ padding: 0.3em 0 0.3em 4em;
+}
+
+.timeline-date-item-badge {
+ display: inline-block;
+ padding: 0.1em 0.4em;
+ border-radius: 0.4em;
+ background: #7c7c7c;
+ color: white;
+ font-size: 0.8em;
+}
+
+.timeline-post-edit-image {
+ max-width: 100px;
+ max-height: 100px;
+}
+
+.mask {
+ background: rgba(255, 255, 255, 0.8);
+ z-index: 100;
+}
+
+.timeline-sync-state-badge {
+ font-size: 0.8em;
+ padding: 3px 8px;
+ border-radius: 5px;
+ background: #e8fbff;
+}
+
+.timeline-sync-state-badge-pin {
+ display: inline-block;
+ width: 0.4em;
+ height: 0.4em;
+ border-radius: 50%;
+ vertical-align: middle;
+ margin-right: 0.6em;
+}
+
+.timeline-template-card {
+ position: fixed;
+ top: 56px;
+ right: 0;
+ margin: 0.5em;
+}
+
+.timeline-markdown-post-edit-page {
+ overflow: scroll;
+ max-height: 300px;
+}
+
+.timeline-markdown-post-edit-image-container {
+ position: relative;
+ text-align: center;
+ margin-bottom: 1em;
+}
+
+.timeline-markdown-post-edit-image {
+ max-width: 100%;
+ max-height: 200px;
+}
+
+.timeline-markdown-post-edit-image-delete-button {
+ position: absolute;
+ right: 10px;
+ top: 2px;
+}
+
+.connection-status-badge {
+ font-size: 0.8em;
+ border-radius: 5px;
+ padding: 0.1em 1em;
+ background-color: #eaf2ff;
+}
+.connection-status-badge::before {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ display: inline-block;
+ content: "";
+ margin-right: 0.6em;
+}
+.connection-status-badge.success {
+ color: #006100;
+}
+.connection-status-badge.success::before {
+ background-color: #006100;
+}
+.connection-status-badge.warning {
+ color: #e4a700;
+}
+.connection-status-badge.warning::before {
+ background-color: #e4a700;
+}
+.connection-status-badge.danger {
+ color: #fd1616;
+}
+.connection-status-badge.danger::before {
+ background-color: #fd1616;
+}
diff --git a/FrontEnd/src/views/timeline-common/timeline-common.sass b/FrontEnd/src/views/timeline-common/timeline-common.sass
deleted file mode 100644
index 4400fead..00000000
--- a/FrontEnd/src/views/timeline-common/timeline-common.sass
+++ /dev/null
@@ -1,259 +0,0 @@
-@use 'sass:color'
-
-.timeline
- z-index: 0
- position: relative
- width: 100%
- overflow-wrap: break-word
- animation: 1s timeline-enter
-
-$timeline-line-width: 7px
-$timeline-line-node-radius: 18px
-$timeline-line-color: var(--tl-primary-color)
-$timeline-line-color-current: var(--tl-primary-enhance-color)
-
-@keyframes timeline-line-node-noncurrent
- to
- box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color)
-
-@keyframes timeline-line-node-current
- to
- box-shadow: 0 0 20px 3px var(--tl-primary-enhance-lighter-color)
-
-@keyframes timeline-line-node-loading
- to
- box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color)
-
-@keyframes timeline-line-node-loading-edge
- from
- transform: rotate(0turn)
- to
- transform: rotate(1turn)
-
-@keyframes timeline-enter
- from
- transform: translate(0, -100vh)
-
-@keyframes timeline-top-loading-enter
- from
- transform: translate(0, -100%)
-
-@keyframes timeline-post-enter
- from
- transform: translate(0, -100%)
- opacity: 0
-
- to
- opacity: 1
-
-.timeline-top-loading-enter
- animation: 1s timeline-top-loading-enter
-
-.timeline-line
- display: flex
- flex-direction: column
- align-items: center
- width: 30px
-
- position: absolute
- z-index: 1
- left: 2em
- top: 0
- bottom: 0
-
- transition: left 0.5s
-
- @include media-breakpoint-down(sm)
- left: 1em
-
- .segment
- width: $timeline-line-width
- background: $timeline-line-color
-
- &.start
- height: 1.8em
- flex: 0 0 auto
-
- &.end
- flex: 1 1 auto
-
- &.current-end
- height: 2em
- flex: 0 0 auto
- background: linear-gradient($timeline-line-color-current, white)
-
- .node-container
- flex: 0 0 auto
- position: relative
- width: $timeline-line-node-radius
- height: $timeline-line-node-radius
-
- .node
- width: $timeline-line-node-radius + 2
- height: $timeline-line-node-radius + 2
- position: absolute
- background: $timeline-line-color
- left: -1px
- top: -1px
- border-radius: 50%
- box-sizing: border-box
- z-index: 1
- animation: 1s infinite alternate
- animation-name: timeline-line-node-noncurrent
-
- .node-loading-edge
- color: $timeline-line-color
- width: $timeline-line-node-radius + 20
- height: $timeline-line-node-radius + 20
- position: absolute
- left: -10px
- top: -10px
- box-sizing: border-box
- z-index: 2
- animation: 1.5s linear infinite timeline-line-node-loading-edge
-
- &.current
- .segment
- &.start
- background: linear-gradient($timeline-line-color, $timeline-line-color-current)
- &.end
- background: $timeline-line-color-current
- .node
- background: $timeline-line-color-current
- animation-name: timeline-line-node-current
-
- &.loading
- .node
- background: $timeline-line-color
- animation-name: timeline-line-node-loading
-
-.timeline-item.current
- padding-bottom: 2.5em
-
-.timeline-top
- position: relative
- text-align: right
-
-.timeline-item
- position: relative
- padding: 0.5em
-
-.timeline-item-card
- @extend .cru-card
- position: relative
- padding: 0.3em 0.5em 1em 4em
- transition: background 0.5s, padding-left 0.5s
- animation: 0.6s forwards
- opacity: 0
-
- @include media-breakpoint-down(sm)
- padding-left: 3em
-
-.timeline-item-header
- display: flex
- align-items: center
- @extend .my-2
-
-.timeline-avatar
- border-radius: 50%
- width: 2em
- height: 2em
-
-.timeline-item-delete-button
- position: absolute
- right: 0
- bottom: 0
-
-.timeline-content
- white-space: pre-line
-
-.timeline-content-image
- max-width: 80%
- max-height: 200px
-
-.timeline-date-item
- position: relative
- padding: 0.3em 0 0.3em 4em
-
-.timeline-date-item-badge
- display: inline-block
- padding: 0.1em 0.4em
- border-radius: 0.4em
- background: #7c7c7c
- color: white
- font-size: 0.8em
-
-.timeline-post-edit-image
- max-width: 100px
- max-height: 100px
-
-.mask
- background: change-color($color: white, $alpha: 0.8)
- z-index: 100
-
-.timeline-sync-state-badge
- font-size: 0.8em
- padding: 3px 8px
- border-radius: 5px
- background: #e8fbff
-
-.timeline-sync-state-badge-pin
- display: inline-block
- width: 0.4em
- height: 0.4em
- border-radius: 50%
- vertical-align: middle
- margin-right: 0.6em
-
-.timeline-template-card
- position: fixed
- top: 56px
- right: 0
- margin: 0.5em
-
-.timeline-markdown-post-edit-page
- overflow: scroll
- max-height: 300px
-
-.timeline-markdown-post-edit-image-container
- position: relative
- text-align: center
- margin-bottom: 1em
-
-.timeline-markdown-post-edit-image
- max-width: 100%
- max-height: 200px
-
-.timeline-markdown-post-edit-image-delete-button
- position: absolute
- right: 10px
- top: 2px
-
-.connection-status-badge
- font-size: 0.8em
- border-radius: 5px
- padding: 0.1em 1em
- background-color: rgb(234 242 255)
-
- &::before
- width: 10px
- height: 10px
- border-radius: 50%
- display: inline-block
- content: ''
- margin-right: 0.6em
-
- &.success
- color: #006100
- &::before
- background-color: #006100
-
- &.warning
- color: #e4a700
- &::before
- background-color: #e4a700
-
- &.danger
- color: #fd1616
- &::before
- background-color: #fd1616
diff --git a/FrontEnd/src/views/timeline/timeline.sass b/FrontEnd/src/views/timeline/timeline.sass
deleted file mode 100644
index e69de29b..00000000
--- a/FrontEnd/src/views/timeline/timeline.sass
+++ /dev/null
diff --git a/FrontEnd/src/views/user/index.css b/FrontEnd/src/views/user/index.css
new file mode 100644
index 00000000..35f01d38
--- /dev/null
+++ b/FrontEnd/src/views/user/index.css
@@ -0,0 +1,9 @@
+.change-avatar-cropper-row {
+ max-height: 400px;
+}
+
+.change-avatar-img {
+ min-width: 50%;
+ max-width: 100%;
+ max-height: 400px;
+}
diff --git a/FrontEnd/src/views/user/index.tsx b/FrontEnd/src/views/user/index.tsx
index 0013b254..1f2fe9ed 100644
--- a/FrontEnd/src/views/user/index.tsx
+++ b/FrontEnd/src/views/user/index.tsx
@@ -4,6 +4,8 @@ import { useParams } from "react-router";
import TimelinePageTemplate from "../timeline-common/TimelinePageTemplate";
import UserCard from "./UserCard";
+import "./index.css";
+
const UserPage: React.FC = () => {
const { username } = useParams<{ username: string }>();
diff --git a/FrontEnd/src/views/user/user.sass b/FrontEnd/src/views/user/user.sass
deleted file mode 100644
index 63a28e05..00000000
--- a/FrontEnd/src/views/user/user.sass
+++ /dev/null
@@ -1,7 +0,0 @@
-.change-avatar-cropper-row
- max-height: 400px
-
-.change-avatar-img
- min-width: 50%
- max-width: 100%
- max-height: 400px