Greums commited on
Commit
1813a37
·
1 Parent(s): 88b6561

crlf to lf

Browse files
src/components/button/index.tsx CHANGED
@@ -1,39 +1,39 @@
1
- import {ComponentChildren, JSX} from 'preact';
2
- import cn from 'classnames';
3
- import style from "./style.module.scss";
4
-
5
- export function Button(props: {
6
- loading?: boolean;
7
- onClick?: () => void;
8
- className?: string;
9
- secondary?: boolean;
10
- disabled?: boolean;
11
- title?: string;
12
- children: ComponentChildren;
13
- }) {
14
- const disabled = props.disabled || props.loading;
15
- const buttonClass = cn(style.btn, {[style.secondary]: props.secondary}, 'button', props.className, {[style.disabled]:disabled});
16
-
17
- let spinner: JSX.Element = undefined;
18
-
19
- if (props.loading) {
20
- spinner = <span className={style.spinner}/>
21
- }
22
-
23
- return (
24
- <button
25
- type="button"
26
- onClick={() => {
27
- if (!disabled) {
28
- props.onClick()
29
- }
30
- }}
31
- className={buttonClass}
32
- disabled={disabled}
33
- title={props.title}
34
- >
35
- {spinner}
36
- {props.children}
37
- </button>
38
- );
39
  }
 
1
+ import {ComponentChildren, JSX} from 'preact';
2
+ import cn from 'classnames';
3
+ import style from "./style.module.scss";
4
+
5
+ export function Button(props: {
6
+ loading?: boolean;
7
+ onClick?: () => void;
8
+ className?: string;
9
+ secondary?: boolean;
10
+ disabled?: boolean;
11
+ title?: string;
12
+ children: ComponentChildren;
13
+ }) {
14
+ const disabled = props.disabled || props.loading;
15
+ const buttonClass = cn(style.btn, {[style.secondary]: props.secondary}, 'button', props.className, {[style.disabled]:disabled});
16
+
17
+ let spinner: JSX.Element = undefined;
18
+
19
+ if (props.loading) {
20
+ spinner = <span className={style.spinner}/>
21
+ }
22
+
23
+ return (
24
+ <button
25
+ type="button"
26
+ onClick={() => {
27
+ if (!disabled) {
28
+ props.onClick()
29
+ }
30
+ }}
31
+ className={buttonClass}
32
+ disabled={disabled}
33
+ title={props.title}
34
+ >
35
+ {spinner}
36
+ {props.children}
37
+ </button>
38
+ );
39
  }
src/components/button/style.module.scss CHANGED
@@ -1,105 +1,105 @@
1
- .btn {
2
- position: relative;
3
- display: inline-flex;
4
- justify-content: center;
5
- align-items: center;
6
- padding: 0 1.25rem;
7
- margin: 0;
8
- border: 0.0625rem solid var(--text-secondary);
9
- height: 2.25rem;
10
- line-height: 2.25rem;
11
- font-size: 0.9375rem;
12
- text-decoration: none;
13
- color: var(--text-secondary);
14
- border-radius: 1.125rem;
15
-
16
- white-space: nowrap;
17
- cursor: pointer;
18
- //overflow: hidden;
19
-
20
- &:not(.disabled):hover {
21
- border-color: var(--text-hover-secondary);
22
- color: var(--text-hover-secondary);
23
- }
24
- //
25
- //&:active {
26
- // //border-color: var(--text-secondary);
27
- // background: var(--text-secondary);
28
- //}
29
-
30
- &.secondary {
31
- color: #fff;
32
- background-color: var(--text-secondary);
33
-
34
- &:not(.disabled):hover {
35
- border-color: var(--text-hover-secondary);
36
- background: var(--text-hover-secondary);
37
- color: #fff;
38
- }
39
- }
40
- }
41
-
42
- .spinner {
43
- font-size: 10px;
44
- //margin: 50px auto;
45
- margin-right: 1em;
46
- width: 1.5em;
47
- height: 1.5em;
48
- border-radius: 50%;
49
- background: #ffffff;
50
- background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
51
- position: relative;
52
- animation: load3 1.4s infinite linear;
53
- transform: translateZ(0);
54
-
55
- &:before {
56
- width: 50%;
57
- height: 50%;
58
- background: #ffffff;
59
- border-radius: 100% 0 0 0;
60
- position: absolute;
61
- top: 0;
62
- left: 0;
63
- content: '';
64
- }
65
-
66
- &:after {
67
- background: var(--text-secondary);
68
- width: 75%;
69
- height: 75%;
70
- border-radius: 50%;
71
- content: '';
72
- margin: auto;
73
- position: absolute;
74
- top: 0;
75
- left: 0;
76
- bottom: 0;
77
- right: 0;
78
- }
79
- }
80
-
81
- @keyframes load3 {
82
- 0% {
83
- transform: rotate(0deg);
84
- }
85
- 100% {
86
- transform: rotate(360deg);
87
- }
88
- }
89
-
90
- .disabled {
91
- cursor: not-allowed;
92
-
93
- &:after {
94
- content: "";
95
- position: absolute;
96
- width: 100%;
97
- height: 100%;
98
- background: #4f4f4f;
99
- border: 0.0625rem solid #4f4f4f;
100
- border-radius: 1.125rem;
101
- opacity: 0.5;
102
- box-sizing: content-box;
103
- }
104
- }
105
-
 
1
+ .btn {
2
+ position: relative;
3
+ display: inline-flex;
4
+ justify-content: center;
5
+ align-items: center;
6
+ padding: 0 1.25rem;
7
+ margin: 0;
8
+ border: 0.0625rem solid var(--text-secondary);
9
+ height: 2.25rem;
10
+ line-height: 2.25rem;
11
+ font-size: 0.9375rem;
12
+ text-decoration: none;
13
+ color: var(--text-secondary);
14
+ border-radius: 1.125rem;
15
+
16
+ white-space: nowrap;
17
+ cursor: pointer;
18
+ //overflow: hidden;
19
+
20
+ &:not(.disabled):hover {
21
+ border-color: var(--text-hover-secondary);
22
+ color: var(--text-hover-secondary);
23
+ }
24
+ //
25
+ //&:active {
26
+ // //border-color: var(--text-secondary);
27
+ // background: var(--text-secondary);
28
+ //}
29
+
30
+ &.secondary {
31
+ color: #fff;
32
+ background-color: var(--text-secondary);
33
+
34
+ &:not(.disabled):hover {
35
+ border-color: var(--text-hover-secondary);
36
+ background: var(--text-hover-secondary);
37
+ color: #fff;
38
+ }
39
+ }
40
+ }
41
+
42
+ .spinner {
43
+ font-size: 10px;
44
+ //margin: 50px auto;
45
+ margin-right: 1em;
46
+ width: 1.5em;
47
+ height: 1.5em;
48
+ border-radius: 50%;
49
+ background: #ffffff;
50
+ background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
51
+ position: relative;
52
+ animation: load3 1.4s infinite linear;
53
+ transform: translateZ(0);
54
+
55
+ &:before {
56
+ width: 50%;
57
+ height: 50%;
58
+ background: #ffffff;
59
+ border-radius: 100% 0 0 0;
60
+ position: absolute;
61
+ top: 0;
62
+ left: 0;
63
+ content: '';
64
+ }
65
+
66
+ &:after {
67
+ background: var(--text-secondary);
68
+ width: 75%;
69
+ height: 75%;
70
+ border-radius: 50%;
71
+ content: '';
72
+ margin: auto;
73
+ position: absolute;
74
+ top: 0;
75
+ left: 0;
76
+ bottom: 0;
77
+ right: 0;
78
+ }
79
+ }
80
+
81
+ @keyframes load3 {
82
+ 0% {
83
+ transform: rotate(0deg);
84
+ }
85
+ 100% {
86
+ transform: rotate(360deg);
87
+ }
88
+ }
89
+
90
+ .disabled {
91
+ cursor: not-allowed;
92
+
93
+ &:after {
94
+ content: "";
95
+ position: absolute;
96
+ width: 100%;
97
+ height: 100%;
98
+ background: #4f4f4f;
99
+ border: 0.0625rem solid #4f4f4f;
100
+ border-radius: 1.125rem;
101
+ opacity: 0.5;
102
+ box-sizing: content-box;
103
+ }
104
+ }
105
+
src/components/container/index.tsx CHANGED
@@ -1,10 +1,10 @@
1
- import type {ComponentChildren} from "preact";
2
- import style from "./style.module.scss"
3
-
4
- export function Container(props: { children: ComponentChildren }) {
5
- return (
6
- <div class={style.container}>
7
- {props.children}
8
- </div>
9
- );
10
  }
 
1
+ import type {ComponentChildren} from "preact";
2
+ import style from "./style.module.scss"
3
+
4
+ export function Container(props: { children: ComponentChildren }) {
5
+ return (
6
+ <div class={style.container}>
7
+ {props.children}
8
+ </div>
9
+ );
10
  }
src/components/container/style.module.scss CHANGED
@@ -1,8 +1,8 @@
1
- .container {
2
- margin: 0 auto;
3
- //min-width: 20rem;
4
- //max-width: 73.5rem;
5
- padding: 0 1rem;
6
-
7
- max-width: 800px;
8
  }
 
1
+ .container {
2
+ margin: 0 auto;
3
+ //min-width: 20rem;
4
+ //max-width: 73.5rem;
5
+ padding: 0 1rem;
6
+
7
+ max-width: 800px;
8
  }
src/components/form/index.tsx CHANGED
@@ -1,12 +1,12 @@
1
- import type {ComponentChildren} from "preact";
2
- import style from "./style.module.scss"
3
-
4
- export function FormGroup(props: {
5
- children: ComponentChildren,
6
- }) {
7
- return (
8
- <div className={style.formGroup}>
9
- {props.children}
10
- </div>
11
- )
12
  }
 
1
+ import type {ComponentChildren} from "preact";
2
+ import style from "./style.module.scss"
3
+
4
+ export function FormGroup(props: {
5
+ children: ComponentChildren,
6
+ }) {
7
+ return (
8
+ <div className={style.formGroup}>
9
+ {props.children}
10
+ </div>
11
+ )
12
  }
src/components/form/style.module.scss CHANGED
@@ -1,15 +1,15 @@
1
- .formGroup {
2
- display: flex;
3
- flex-direction: column;
4
- margin-bottom: 1rem;
5
-
6
- justify-content: space-between;
7
- & > * {
8
- flex-grow: 0;
9
- }
10
-
11
- label {
12
- font-size: 1rem;
13
- margin-bottom: .5rem;
14
- }
15
  }
 
1
+ .formGroup {
2
+ display: flex;
3
+ flex-direction: column;
4
+ margin-bottom: 1rem;
5
+
6
+ justify-content: space-between;
7
+ & > * {
8
+ flex-grow: 0;
9
+ }
10
+
11
+ label {
12
+ font-size: 1rem;
13
+ margin-bottom: .5rem;
14
+ }
15
  }
src/components/input/index.tsx CHANGED
@@ -1,86 +1,86 @@
1
- import {FunctionalComponent} from "preact";
2
- import {useState} from "preact/hooks";
3
- import cn from "classnames";
4
- import {JSX} from "preact";
5
- // import GenericEventHandler = JSXInternal.GenericEventHandler;
6
- import style from "./style.module.scss";
7
- import {Settings} from "preact-feather";
8
- import {FeatherProps} from "preact-feather/src/types";
9
-
10
- interface InputProps<T> {
11
- type: "text" | "number" | "email" | "password";
12
- icon: FunctionalComponent<FeatherProps>;
13
- value: T;
14
- placeholder?: string;
15
- onChange: (value: T) => void;
16
- className?: string;
17
- disabled?: boolean;
18
- id?: string;
19
- name?: string;
20
- }
21
-
22
- export const Input: FunctionalComponent<InputProps<string | number>> = ({
23
- type,
24
- icon,
25
- value,
26
- placeholder,
27
- onChange,
28
- className,
29
- disabled,
30
- id,
31
- name,
32
- }) => {
33
- const [focused, setFocused] = useState(false);
34
-
35
- const inputClass = cn(style.input, "generic-input", className, {
36
- focused,
37
- disabled,
38
- });
39
-
40
- const handleInputChange: JSX.GenericEventHandler<HTMLInputElement> = (event) => {
41
- console.log("handleInputChange")
42
- const target = event.target as HTMLInputElement;
43
- onChange(type === "number" ? (parseFloat(target.value) || 0) : target.value);
44
- };
45
-
46
- const Icon = icon;
47
-
48
- return (
49
- <div className={style.wrapper}>
50
- <Icon className={style.icon} size={18}/>
51
- <input
52
- // required=""
53
- // minLength="3"
54
- // maxLength="15"
55
- title="Le pseudo doit avoir une longueur comprise entre 3 et 15 caractères."
56
- // tabIndex="10"
57
- type={type}
58
- id={id}
59
- name={name}
60
- value={value}
61
- placeholder={placeholder}
62
- // onChange={handleInputChange}
63
- onInput={handleInputChange}
64
- className={inputClass}
65
- disabled={disabled}
66
- onFocus={() => setFocused(true)}
67
- onBlur={() => setFocused(false)}
68
- />
69
- </div>
70
- )
71
-
72
- return (
73
- <input
74
- type={type}
75
- id={id}
76
- name={name}
77
- value={value}
78
- placeholder={placeholder}
79
- onChange={handleInputChange}
80
- className={inputClass}
81
- disabled={disabled}
82
- onFocus={() => setFocused(true)}
83
- onBlur={() => setFocused(false)}
84
- />
85
- );
86
  };
 
1
+ import {FunctionalComponent} from "preact";
2
+ import {useState} from "preact/hooks";
3
+ import cn from "classnames";
4
+ import {JSX} from "preact";
5
+ // import GenericEventHandler = JSXInternal.GenericEventHandler;
6
+ import style from "./style.module.scss";
7
+ import {Settings} from "preact-feather";
8
+ import {FeatherProps} from "preact-feather/src/types";
9
+
10
+ interface InputProps<T> {
11
+ type: "text" | "number" | "email" | "password";
12
+ icon: FunctionalComponent<FeatherProps>;
13
+ value: T;
14
+ placeholder?: string;
15
+ onChange: (value: T) => void;
16
+ className?: string;
17
+ disabled?: boolean;
18
+ id?: string;
19
+ name?: string;
20
+ }
21
+
22
+ export const Input: FunctionalComponent<InputProps<string | number>> = ({
23
+ type,
24
+ icon,
25
+ value,
26
+ placeholder,
27
+ onChange,
28
+ className,
29
+ disabled,
30
+ id,
31
+ name,
32
+ }) => {
33
+ const [focused, setFocused] = useState(false);
34
+
35
+ const inputClass = cn(style.input, "generic-input", className, {
36
+ focused,
37
+ disabled,
38
+ });
39
+
40
+ const handleInputChange: JSX.GenericEventHandler<HTMLInputElement> = (event) => {
41
+ console.log("handleInputChange")
42
+ const target = event.target as HTMLInputElement;
43
+ onChange(type === "number" ? (parseFloat(target.value) || 0) : target.value);
44
+ };
45
+
46
+ const Icon = icon;
47
+
48
+ return (
49
+ <div className={style.wrapper}>
50
+ <Icon className={style.icon} size={18}/>
51
+ <input
52
+ // required=""
53
+ // minLength="3"
54
+ // maxLength="15"
55
+ title="Le pseudo doit avoir une longueur comprise entre 3 et 15 caractères."
56
+ // tabIndex="10"
57
+ type={type}
58
+ id={id}
59
+ name={name}
60
+ value={value}
61
+ placeholder={placeholder}
62
+ // onChange={handleInputChange}
63
+ onInput={handleInputChange}
64
+ className={inputClass}
65
+ disabled={disabled}
66
+ onFocus={() => setFocused(true)}
67
+ onBlur={() => setFocused(false)}
68
+ />
69
+ </div>
70
+ )
71
+
72
+ return (
73
+ <input
74
+ type={type}
75
+ id={id}
76
+ name={name}
77
+ value={value}
78
+ placeholder={placeholder}
79
+ onChange={handleInputChange}
80
+ className={inputClass}
81
+ disabled={disabled}
82
+ onFocus={() => setFocused(true)}
83
+ onBlur={() => setFocused(false)}
84
+ />
85
+ );
86
  };
src/components/input/style.module.scss CHANGED
@@ -1,45 +1,45 @@
1
- .wrapper {
2
- position: relative;
3
- margin-bottom: 0.9375rem
4
- }
5
-
6
- .icon {
7
- position: absolute;
8
- top: .625rem;
9
- left: .625rem;
10
- font-size: 1.125rem;
11
- line-height: 1em;
12
- }
13
-
14
- .input {
15
-
16
- display: block;
17
- width: 100%;
18
- //padding: 0.4375rem 0.75rem;
19
- //font-size: 0.9375rem;
20
- font-weight: 400;
21
- line-height: 1.5;
22
- color: var(--input-text-color);
23
- background-color: var(--input-bg-color);
24
- background-clip: padding-box;
25
- border: 0.0625rem solid var(--input-border-color);
26
- -webkit-appearance: none;
27
- appearance: none;
28
- border-radius: 0.75rem;
29
- box-shadow: inset 0 0.0625rem 0.125rem rgba(0, 0, 0, .075);
30
- transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
31
-
32
- padding-top: 0;
33
- padding-right: .3125rem;
34
- //padding-left: 0.4375rem;
35
- padding-left: 2.5rem;
36
- padding-bottom: 0;
37
- font-weight: 700;
38
- height: 2.5rem;
39
- font-size: 1rem;
40
-
41
- &::placeholder {
42
- color: var(--input-placeholder-color);
43
- font-weight: 400;
44
- }
45
  }
 
1
+ .wrapper {
2
+ position: relative;
3
+ margin-bottom: 0.9375rem
4
+ }
5
+
6
+ .icon {
7
+ position: absolute;
8
+ top: .625rem;
9
+ left: .625rem;
10
+ font-size: 1.125rem;
11
+ line-height: 1em;
12
+ }
13
+
14
+ .input {
15
+
16
+ display: block;
17
+ width: 100%;
18
+ //padding: 0.4375rem 0.75rem;
19
+ //font-size: 0.9375rem;
20
+ font-weight: 400;
21
+ line-height: 1.5;
22
+ color: var(--input-text-color);
23
+ background-color: var(--input-bg-color);
24
+ background-clip: padding-box;
25
+ border: 0.0625rem solid var(--input-border-color);
26
+ -webkit-appearance: none;
27
+ appearance: none;
28
+ border-radius: 0.75rem;
29
+ box-shadow: inset 0 0.0625rem 0.125rem rgba(0, 0, 0, .075);
30
+ transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
31
+
32
+ padding-top: 0;
33
+ padding-right: .3125rem;
34
+ //padding-left: 0.4375rem;
35
+ padding-left: 2.5rem;
36
+ padding-bottom: 0;
37
+ font-weight: 700;
38
+ height: 2.5rem;
39
+ font-size: 1rem;
40
+
41
+ &::placeholder {
42
+ color: var(--input-placeholder-color);
43
+ font-weight: 400;
44
+ }
45
  }
src/components/layout/index.tsx CHANGED
@@ -1,29 +1,29 @@
1
- import type {ComponentChildren} from "preact";
2
- import style from "./style.module.scss"
3
- import {Settings} from "preact-feather"
4
- import {Route, routes} from "../../utils/route";
5
-
6
- export function Layout(props: {
7
- breadcrumbs: string,
8
- title: string,
9
- setRoute: (route: Route) => void,
10
- children: ComponentChildren
11
- }) {
12
- return(
13
- <div>
14
- <nav className={style.breadcrumbs}>
15
- {props.breadcrumbs}
16
- <div className={style.actions}>
17
- <a href="#" title="Paramètres" onClick={e => {
18
- e.preventDefault();
19
- props.setRoute(routes.settings);
20
- }}>
21
- <Settings size={18}/>
22
- </a>
23
- </div>
24
- </nav>
25
- <h2>{props.title}</h2>
26
- {props.children}
27
- </div>
28
- )
29
  }
 
1
+ import type {ComponentChildren} from "preact";
2
+ import style from "./style.module.scss"
3
+ import {Settings} from "preact-feather"
4
+ import {Route, routes} from "../../utils/route";
5
+
6
+ export function Layout(props: {
7
+ breadcrumbs: string,
8
+ title: string,
9
+ setRoute: (route: Route) => void,
10
+ children: ComponentChildren
11
+ }) {
12
+ return(
13
+ <div>
14
+ <nav className={style.breadcrumbs}>
15
+ {props.breadcrumbs}
16
+ <div className={style.actions}>
17
+ <a href="#" title="Paramètres" onClick={e => {
18
+ e.preventDefault();
19
+ props.setRoute(routes.settings);
20
+ }}>
21
+ <Settings size={18}/>
22
+ </a>
23
+ </div>
24
+ </nav>
25
+ <h2>{props.title}</h2>
26
+ {props.children}
27
+ </div>
28
+ )
29
  }
src/components/preview/style.module.scss CHANGED
@@ -1,103 +1,103 @@
1
- .wrapper {
2
- //white-space: pre-line;
3
-
4
- :global {
5
- .bloc-spoil-jv {
6
- //margin-bottom: 0.9375rem;
7
- margin-bottom: 0;
8
- display: inline;
9
-
10
- .open-spoil {
11
- position: absolute;
12
- left: -999rem;
13
- }
14
-
15
- .barre-head {
16
- //height: 1.5rem;
17
- //line-height: 1.5rem;
18
- //cursor: pointer;
19
- //position: relative;
20
- //display: block;
21
-
22
- height: auto;
23
- line-height: inherit;
24
- text-align: center;
25
- cursor: pointer;
26
- position: relative;
27
- display: inline;
28
-
29
- .txt-spoil {
30
- //position: absolute;
31
- //top: 0;
32
- //left: 0;
33
- //width: 4.6875rem;
34
- //font-size: .9285em;
35
- //text-align: center;
36
- //color: #fff;
37
- //text-transform: uppercase;
38
- //background: #fd374e;
39
- //font-weight: 700;
40
- //display: block;
41
- //border-radius: 0.25rem;
42
-
43
- position: static;
44
- display: inline;
45
- padding: 0.125rem 1.25rem;
46
- line-height: inherit;
47
- top: 0;
48
- left: 0;
49
- width: 4.6875rem;
50
- font-size: .9285em;
51
- text-align: center;
52
- color: #fff;
53
- text-transform: uppercase;
54
- background: #fd374e;
55
- font-weight: 700;
56
- //display: block;
57
- border-radius: 0.25rem;
58
- }
59
- }
60
-
61
- .contenu-spoil {
62
- background: #f4d6da;
63
- color: #212121;
64
- padding: 0.625rem;
65
- display: none;
66
- //text-align: justify;
67
- text-align: left;
68
- overflow: hidden;
69
- }
70
-
71
- .open-spoil:checked ~ .contenu-spoil {
72
- display: inline;
73
- }
74
- }
75
- }
76
-
77
- blockquote {
78
- text-align: left;
79
- //margin-bottom: 0.9375rem;
80
- border-left: 0.3125rem solid rgba(0, 0, 0, .2);
81
- color: var(--text-blockquote-color);
82
- //margin-left: 0.9375rem;
83
- padding: 0.625rem;
84
- background: none;
85
- font-style: normal;
86
- font-size: inherit;
87
- margin: 0 0 0.9375rem 1rem;
88
- }
89
-
90
- a {
91
- color: var(--link-color);
92
- text-decoration: none;
93
-
94
- &:hover {
95
- color: var(--text-hover-secondary);
96
- text-decoration: none;
97
- }
98
- }
99
- }
100
-
101
- .img {
102
-
103
  }
 
1
+ .wrapper {
2
+ //white-space: pre-line;
3
+
4
+ :global {
5
+ .bloc-spoil-jv {
6
+ //margin-bottom: 0.9375rem;
7
+ margin-bottom: 0;
8
+ display: inline;
9
+
10
+ .open-spoil {
11
+ position: absolute;
12
+ left: -999rem;
13
+ }
14
+
15
+ .barre-head {
16
+ //height: 1.5rem;
17
+ //line-height: 1.5rem;
18
+ //cursor: pointer;
19
+ //position: relative;
20
+ //display: block;
21
+
22
+ height: auto;
23
+ line-height: inherit;
24
+ text-align: center;
25
+ cursor: pointer;
26
+ position: relative;
27
+ display: inline;
28
+
29
+ .txt-spoil {
30
+ //position: absolute;
31
+ //top: 0;
32
+ //left: 0;
33
+ //width: 4.6875rem;
34
+ //font-size: .9285em;
35
+ //text-align: center;
36
+ //color: #fff;
37
+ //text-transform: uppercase;
38
+ //background: #fd374e;
39
+ //font-weight: 700;
40
+ //display: block;
41
+ //border-radius: 0.25rem;
42
+
43
+ position: static;
44
+ display: inline;
45
+ padding: 0.125rem 1.25rem;
46
+ line-height: inherit;
47
+ top: 0;
48
+ left: 0;
49
+ width: 4.6875rem;
50
+ font-size: .9285em;
51
+ text-align: center;
52
+ color: #fff;
53
+ text-transform: uppercase;
54
+ background: #fd374e;
55
+ font-weight: 700;
56
+ //display: block;
57
+ border-radius: 0.25rem;
58
+ }
59
+ }
60
+
61
+ .contenu-spoil {
62
+ background: #f4d6da;
63
+ color: #212121;
64
+ padding: 0.625rem;
65
+ display: none;
66
+ //text-align: justify;
67
+ text-align: left;
68
+ overflow: hidden;
69
+ }
70
+
71
+ .open-spoil:checked ~ .contenu-spoil {
72
+ display: inline;
73
+ }
74
+ }
75
+ }
76
+
77
+ blockquote {
78
+ text-align: left;
79
+ //margin-bottom: 0.9375rem;
80
+ border-left: 0.3125rem solid rgba(0, 0, 0, .2);
81
+ color: var(--text-blockquote-color);
82
+ //margin-left: 0.9375rem;
83
+ padding: 0.625rem;
84
+ background: none;
85
+ font-style: normal;
86
+ font-size: inherit;
87
+ margin: 0 0 0.9375rem 1rem;
88
+ }
89
+
90
+ a {
91
+ color: var(--link-color);
92
+ text-decoration: none;
93
+
94
+ &:hover {
95
+ color: var(--text-hover-secondary);
96
+ text-decoration: none;
97
+ }
98
+ }
99
+ }
100
+
101
+ .img {
102
+
103
  }
src/components/settings/index.tsx CHANGED
@@ -1,66 +1,66 @@
1
- import {Input} from "../input";
2
- import {Link, Thermometer, Sliders} from "preact-feather";
3
- import {Button} from "../button";
4
- import {useEffect, useState} from "preact/hooks";
5
- import {fetchSettings, saveSettings, Settings as SettingsType} from "../../utils/settings";
6
- import {Slider} from "../slider";
7
- import {FormGroup} from "../form";
8
-
9
- export function Settings(props: {
10
- settings: SettingsType,
11
- setSettings: (settings: SettingsType) => void,
12
- resetApp: () => void,
13
- }) {
14
- // const [settings, setSettings] = useState(fetchSettings)
15
- //
16
- // useEffect(() => {
17
- // saveSettings(settings);
18
- // }, [settings]);
19
-
20
- return <div>
21
- <form>
22
- <FormGroup>
23
- <label htmlFor="api">API</label>
24
- <Input
25
- type="text"
26
- placeholder="URl d'API ex: https://ouruq7zepnehg2-5000.proxy.runpod.net/"
27
- icon={Link}
28
- value={props.settings.apiURL}
29
- onChange={(v) => props.setSettings({...props.settings, apiURL: v as string})}
30
- />
31
- </FormGroup>
32
- <FormGroup>
33
- <label for="temperature">Temperature</label>
34
- <Slider
35
- name="temperature"
36
- value={props.settings.temperature}
37
- onChange={(v) => props.setSettings({...props.settings, temperature: v})}
38
- min={0.1}
39
- max={2}
40
- step={0.1}
41
- />
42
- </FormGroup>
43
- <div>
44
- <Button
45
- onClick={() => {
46
- props.resetApp()
47
- }}
48
- secondary={true}
49
- title="Tout réinitialiser"
50
- >
51
- Réinitialiser
52
- </Button>
53
- </div>
54
- <br/>
55
- <div>
56
- <Button
57
- onClick={() => {
58
- history.go(-1)
59
- }}
60
- >
61
- Retour
62
- </Button>
63
- </div>
64
- </form>
65
- </div>
66
  }
 
1
+ import {Input} from "../input";
2
+ import {Link, Thermometer, Sliders} from "preact-feather";
3
+ import {Button} from "../button";
4
+ import {useEffect, useState} from "preact/hooks";
5
+ import {fetchSettings, saveSettings, Settings as SettingsType} from "../../utils/settings";
6
+ import {Slider} from "../slider";
7
+ import {FormGroup} from "../form";
8
+
9
+ export function Settings(props: {
10
+ settings: SettingsType,
11
+ setSettings: (settings: SettingsType) => void,
12
+ resetApp: () => void,
13
+ }) {
14
+ // const [settings, setSettings] = useState(fetchSettings)
15
+ //
16
+ // useEffect(() => {
17
+ // saveSettings(settings);
18
+ // }, [settings]);
19
+
20
+ return <div>
21
+ <form>
22
+ <FormGroup>
23
+ <label htmlFor="api">API</label>
24
+ <Input
25
+ type="text"
26
+ placeholder="URl d'API ex: https://ouruq7zepnehg2-5000.proxy.runpod.net/"
27
+ icon={Link}
28
+ value={props.settings.apiURL}
29
+ onChange={(v) => props.setSettings({...props.settings, apiURL: v as string})}
30
+ />
31
+ </FormGroup>
32
+ <FormGroup>
33
+ <label for="temperature">Temperature</label>
34
+ <Slider
35
+ name="temperature"
36
+ value={props.settings.temperature}
37
+ onChange={(v) => props.setSettings({...props.settings, temperature: v})}
38
+ min={0.1}
39
+ max={2}
40
+ step={0.1}
41
+ />
42
+ </FormGroup>
43
+ <div>
44
+ <Button
45
+ onClick={() => {
46
+ props.resetApp()
47
+ }}
48
+ secondary={true}
49
+ title="Tout réinitialiser"
50
+ >
51
+ Réinitialiser
52
+ </Button>
53
+ </div>
54
+ <br/>
55
+ <div>
56
+ <Button
57
+ onClick={() => {
58
+ history.go(-1)
59
+ }}
60
+ >
61
+ Retour
62
+ </Button>
63
+ </div>
64
+ </form>
65
+ </div>
66
  }
src/components/slider/style.module.scss CHANGED
@@ -1,146 +1,146 @@
1
- $ticks-height: 5px;
2
- $ticks-thickness: 1px;
3
- $max-ticks-allowed: 30;
4
- $thumb-size: 20px;
5
- $track-height: #{calc($thumb-size / 2)};
6
-
7
- .slider {
8
- // Define css variables
9
- --ticks-count: calc(var(--max) - var(--min)) / var(--step);
10
- --too-many-ticks: min(1, Max(var(--ticks-count) - #{$max-ticks-allowed}, 0));
11
- --x-step: max(var(--step), var(--too-many-ticks) * (var(--max) - var(--min)));
12
-
13
- // Define properties
14
- position: relative;
15
-
16
- // Draw the steps
17
- background: linear-gradient(to right, var(--text-muted-color) $ticks-thickness, transparent 1px) repeat-x;
18
- background-size: calc((100% - #{$thumb-size}) / ((var(--max) - var(--min)) / var(--x-step))) #{$ticks-height};
19
- background-position-x: #{calc($track-height - $ticks-thickness / 2)};
20
- background-position-y: bottom;
21
-
22
- font-weight: 700;
23
- color: var(--text-muted-color);
24
- font-size: 0.8125rem;
25
- height: 22px;
26
- margin-top: 2.5ch;
27
- margin-bottom: 2.5ch;
28
-
29
- // mix/max texts
30
- &::before,
31
- &::after {
32
- content: counter(x);
33
- position: absolute;
34
- bottom: calc(-2.5ch - 2px);
35
- top: calc(2.5ch + 2px);
36
- opacity: .5;
37
- transform: translateX(calc(50% * var(--before, -1) * -1));
38
- pointer-events: none;
39
- }
40
-
41
- &::before {
42
- --before: 1;
43
- counter-reset: x var(--min);
44
- left: #{$track-height};
45
- }
46
-
47
- &::after {
48
- counter-reset: x var(--max);
49
- right: #{$track-height};
50
- }
51
- }
52
-
53
- .outputContainer {
54
- position: absolute;
55
- top: 0;
56
- left: #{$track-height};
57
- right: #{$track-height};
58
- height: #{$track-height};
59
- pointer-events: none;
60
- }
61
-
62
- .output {
63
- pointer-events: none;
64
- position: absolute;
65
- z-index: 5;
66
- width: #{$thumb-size};
67
- height: #{$thumb-size};
68
- left: calc((((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100%) - #{calc($thumb-size / 2)});
69
- bottom: #{calc($thumb-size / 2 + 6px)};
70
- text-align: center;
71
- }
72
-
73
-
74
-
75
- .progress {
76
- --completed-a: calc((clamp(var(--min), var(--value, 0), var(--max)) - var(--min)) / (var(--max) - var(--min)) * 100);
77
- --completed-b: calc((var(--value) - var(--min)) / (var(--max) - var(--min)) * 100);
78
- --cb: max(var(--completed-a), var(--completed-b));
79
- --start-end: #{$track-height};
80
- --clip-end: calc(100% - (var(--cb)) * 1%);
81
- --clip: inset(-20px var(--clip-end) -20px 0);
82
-
83
- position: absolute;
84
- width: 100%;
85
- height: #{$track-height};
86
- background: var(--text-color);
87
- border-radius: #{$track-height};
88
- z-index: -1;
89
-
90
- // fill area
91
- &::before {
92
- content: "";
93
- position: absolute;
94
- left: 0;
95
- right: 0;
96
- clip-path: var(--clip);
97
- top: 0;
98
- bottom: 0;
99
- background: var(--text-secondary);
100
- z-index: 1;
101
- border-radius: inherit;
102
- }
103
- }
104
-
105
- .input {
106
- --thumb-shadow: 0 0 3px rgba(0, 0, 0, 0.4), 0 0 1px rgba(0, 0, 0, 0.5) inset, 0 0 0 99px var(--text-color) inset;
107
-
108
- -webkit-appearance: none;
109
- width: 100%;
110
- height: #{$thumb-size};
111
- margin: 0;
112
- position: absolute;
113
- left: 0;
114
- cursor: grab;
115
- outline: none;
116
- background: none;
117
-
118
- @mixin thumb {
119
- appearance: none;
120
- height: #{$thumb-size};
121
- width: #{$thumb-size};
122
- transform: translateY(-5px);
123
- border-radius: 50%;
124
- background: var(--text-color);
125
- box-shadow: var(--thumb-shadow);
126
- border: none;
127
- pointer-events: auto;
128
- }
129
-
130
- &::-webkit-slider-thumb {
131
- @include thumb;
132
- }
133
-
134
- &::-moz-range-thumb {
135
- @include thumb;
136
- }
137
-
138
- &::-ms-thumb {
139
- @include thumb;
140
- }
141
-
142
- &:active {
143
- --thumb-shadow: 0 0 0 #{calc($thumb-size / 4)} inset var(--text-color), 0 0 0 99px var(--text-secondary) inset, 0 0 3px rgba(0, 0, 0, 0.4);
144
- cursor: grabbing;
145
- }
146
  }
 
1
+ $ticks-height: 5px;
2
+ $ticks-thickness: 1px;
3
+ $max-ticks-allowed: 30;
4
+ $thumb-size: 20px;
5
+ $track-height: #{calc($thumb-size / 2)};
6
+
7
+ .slider {
8
+ // Define css variables
9
+ --ticks-count: calc(var(--max) - var(--min)) / var(--step);
10
+ --too-many-ticks: min(1, Max(var(--ticks-count) - #{$max-ticks-allowed}, 0));
11
+ --x-step: max(var(--step), var(--too-many-ticks) * (var(--max) - var(--min)));
12
+
13
+ // Define properties
14
+ position: relative;
15
+
16
+ // Draw the steps
17
+ background: linear-gradient(to right, var(--text-muted-color) $ticks-thickness, transparent 1px) repeat-x;
18
+ background-size: calc((100% - #{$thumb-size}) / ((var(--max) - var(--min)) / var(--x-step))) #{$ticks-height};
19
+ background-position-x: #{calc($track-height - $ticks-thickness / 2)};
20
+ background-position-y: bottom;
21
+
22
+ font-weight: 700;
23
+ color: var(--text-muted-color);
24
+ font-size: 0.8125rem;
25
+ height: 22px;
26
+ margin-top: 2.5ch;
27
+ margin-bottom: 2.5ch;
28
+
29
+ // mix/max texts
30
+ &::before,
31
+ &::after {
32
+ content: counter(x);
33
+ position: absolute;
34
+ bottom: calc(-2.5ch - 2px);
35
+ top: calc(2.5ch + 2px);
36
+ opacity: .5;
37
+ transform: translateX(calc(50% * var(--before, -1) * -1));
38
+ pointer-events: none;
39
+ }
40
+
41
+ &::before {
42
+ --before: 1;
43
+ counter-reset: x var(--min);
44
+ left: #{$track-height};
45
+ }
46
+
47
+ &::after {
48
+ counter-reset: x var(--max);
49
+ right: #{$track-height};
50
+ }
51
+ }
52
+
53
+ .outputContainer {
54
+ position: absolute;
55
+ top: 0;
56
+ left: #{$track-height};
57
+ right: #{$track-height};
58
+ height: #{$track-height};
59
+ pointer-events: none;
60
+ }
61
+
62
+ .output {
63
+ pointer-events: none;
64
+ position: absolute;
65
+ z-index: 5;
66
+ width: #{$thumb-size};
67
+ height: #{$thumb-size};
68
+ left: calc((((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100%) - #{calc($thumb-size / 2)});
69
+ bottom: #{calc($thumb-size / 2 + 6px)};
70
+ text-align: center;
71
+ }
72
+
73
+
74
+
75
+ .progress {
76
+ --completed-a: calc((clamp(var(--min), var(--value, 0), var(--max)) - var(--min)) / (var(--max) - var(--min)) * 100);
77
+ --completed-b: calc((var(--value) - var(--min)) / (var(--max) - var(--min)) * 100);
78
+ --cb: max(var(--completed-a), var(--completed-b));
79
+ --start-end: #{$track-height};
80
+ --clip-end: calc(100% - (var(--cb)) * 1%);
81
+ --clip: inset(-20px var(--clip-end) -20px 0);
82
+
83
+ position: absolute;
84
+ width: 100%;
85
+ height: #{$track-height};
86
+ background: var(--text-color);
87
+ border-radius: #{$track-height};
88
+ z-index: -1;
89
+
90
+ // fill area
91
+ &::before {
92
+ content: "";
93
+ position: absolute;
94
+ left: 0;
95
+ right: 0;
96
+ clip-path: var(--clip);
97
+ top: 0;
98
+ bottom: 0;
99
+ background: var(--text-secondary);
100
+ z-index: 1;
101
+ border-radius: inherit;
102
+ }
103
+ }
104
+
105
+ .input {
106
+ --thumb-shadow: 0 0 3px rgba(0, 0, 0, 0.4), 0 0 1px rgba(0, 0, 0, 0.5) inset, 0 0 0 99px var(--text-color) inset;
107
+
108
+ -webkit-appearance: none;
109
+ width: 100%;
110
+ height: #{$thumb-size};
111
+ margin: 0;
112
+ position: absolute;
113
+ left: 0;
114
+ cursor: grab;
115
+ outline: none;
116
+ background: none;
117
+
118
+ @mixin thumb {
119
+ appearance: none;
120
+ height: #{$thumb-size};
121
+ width: #{$thumb-size};
122
+ transform: translateY(-5px);
123
+ border-radius: 50%;
124
+ background: var(--text-color);
125
+ box-shadow: var(--thumb-shadow);
126
+ border: none;
127
+ pointer-events: auto;
128
+ }
129
+
130
+ &::-webkit-slider-thumb {
131
+ @include thumb;
132
+ }
133
+
134
+ &::-moz-range-thumb {
135
+ @include thumb;
136
+ }
137
+
138
+ &::-ms-thumb {
139
+ @include thumb;
140
+ }
141
+
142
+ &:active {
143
+ --thumb-shadow: 0 0 0 #{calc($thumb-size / 4)} inset var(--text-color), 0 0 0 99px var(--text-secondary) inset, 0 0 3px rgba(0, 0, 0, 0.4);
144
+ cursor: grabbing;
145
+ }
146
  }
src/components/spinner/index.tsx CHANGED
@@ -1,12 +1,12 @@
1
- import cn from "classnames"
2
- import style from "./style.module.scss"
3
-
4
- export function Spinner(props: {className?: string}) {
5
- return (
6
- <div className={cn(style.spinnerSquare, props.className)}>
7
- <div className={style.square1}></div>
8
- <div className={style.square2}></div>
9
- <div className={style.square3}></div>
10
- </div>
11
- )
12
  }
 
1
+ import cn from "classnames"
2
+ import style from "./style.module.scss"
3
+
4
+ export function Spinner(props: {className?: string}) {
5
+ return (
6
+ <div className={cn(style.spinnerSquare, props.className)}>
7
+ <div className={style.square1}></div>
8
+ <div className={style.square2}></div>
9
+ <div className={style.square3}></div>
10
+ </div>
11
+ )
12
  }
src/components/spinner/style.module.scss CHANGED
@@ -1,46 +1,46 @@
1
- .spinnerSquare {
2
- display: flex;
3
- flex-direction: row;
4
- width: 90px;
5
- height: 120px;
6
-
7
- > div {
8
- width: 17px;
9
- height: 80px;
10
- margin: auto auto;
11
- border-radius: 4px;
12
- }
13
- }
14
-
15
- .square1 {
16
- animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 0s infinite;
17
- }
18
-
19
- .square2 {
20
- animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 200ms infinite;
21
- }
22
-
23
- .square3 {
24
- animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 400ms infinite;
25
- }
26
-
27
- @keyframes square-anim {
28
- 0% {
29
- height: 80px;
30
- background-color: var(--text-secondary);
31
- }
32
- 20% {
33
- height: 80px;
34
- }
35
- 40% {
36
- height: 120px;
37
- background-color: var(--text-hover-secondary);
38
- }
39
- 80% {
40
- height: 80px;
41
- }
42
- 100% {
43
- height: 80px;
44
- background-color: var(--text-secondary);
45
- }
46
  }
 
1
+ .spinnerSquare {
2
+ display: flex;
3
+ flex-direction: row;
4
+ width: 90px;
5
+ height: 120px;
6
+
7
+ > div {
8
+ width: 17px;
9
+ height: 80px;
10
+ margin: auto auto;
11
+ border-radius: 4px;
12
+ }
13
+ }
14
+
15
+ .square1 {
16
+ animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 0s infinite;
17
+ }
18
+
19
+ .square2 {
20
+ animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 200ms infinite;
21
+ }
22
+
23
+ .square3 {
24
+ animation: square-anim 1200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) 400ms infinite;
25
+ }
26
+
27
+ @keyframes square-anim {
28
+ 0% {
29
+ height: 80px;
30
+ background-color: var(--text-secondary);
31
+ }
32
+ 20% {
33
+ height: 80px;
34
+ }
35
+ 40% {
36
+ height: 120px;
37
+ background-color: var(--text-hover-secondary);
38
+ }
39
+ 80% {
40
+ height: 80px;
41
+ }
42
+ 100% {
43
+ height: 80px;
44
+ background-color: var(--text-secondary);
45
+ }
46
  }
src/components/topic/style.module.scss CHANGED
@@ -1,51 +1,51 @@
1
- .post {
2
- margin-bottom: 0.9375rem;
3
- font-size: .9375rem;
4
- line-height: 1.5;
5
- background: var(--block-bg-color);
6
- border: 0.0625rem solid var(--border-color);
7
- border-radius: 0.5rem;
8
- overflow: hidden;
9
- padding: 0.75rem;
10
- }
11
-
12
- .postHeader {
13
- border-bottom: 0.0625rem dotted var(--border-color);
14
- height: 3.625rem;
15
- margin-bottom: 0.75rem;
16
- display: grid;
17
- grid-template-areas:
18
- "avatar user"
19
- "avatar date";
20
- grid-template-rows: 1.5rem 1.5rem;
21
- grid-template-columns: auto 1fr;
22
- column-gap: .625rem;
23
- }
24
-
25
- .avatar {
26
- grid-area: avatar;
27
- height: 3rem;
28
- width: 3rem;
29
- object-fit: cover;
30
- border-radius: 50%;
31
- }
32
-
33
- .user {
34
- grid-area: user;
35
- font-size: 1.0625rem;
36
- font-weight: 500;
37
- font-family: "Roboto", Arial, Helvetica, sans-serif;
38
- line-height: 1.25;
39
- }
40
-
41
- .date {
42
- grid-area: date;
43
- color: var(--link-color);
44
- text-decoration: none;
45
- font-size: .8125rem;
46
- }
47
-
48
- .generationSettings {
49
- display: grid;
50
- grid-template-columns: 1fr 1fr;
51
  }
 
1
+ .post {
2
+ margin-bottom: 0.9375rem;
3
+ font-size: .9375rem;
4
+ line-height: 1.5;
5
+ background: var(--block-bg-color);
6
+ border: 0.0625rem solid var(--border-color);
7
+ border-radius: 0.5rem;
8
+ overflow: hidden;
9
+ padding: 0.75rem;
10
+ }
11
+
12
+ .postHeader {
13
+ border-bottom: 0.0625rem dotted var(--border-color);
14
+ height: 3.625rem;
15
+ margin-bottom: 0.75rem;
16
+ display: grid;
17
+ grid-template-areas:
18
+ "avatar user"
19
+ "avatar date";
20
+ grid-template-rows: 1.5rem 1.5rem;
21
+ grid-template-columns: auto 1fr;
22
+ column-gap: .625rem;
23
+ }
24
+
25
+ .avatar {
26
+ grid-area: avatar;
27
+ height: 3rem;
28
+ width: 3rem;
29
+ object-fit: cover;
30
+ border-radius: 50%;
31
+ }
32
+
33
+ .user {
34
+ grid-area: user;
35
+ font-size: 1.0625rem;
36
+ font-weight: 500;
37
+ font-family: "Roboto", Arial, Helvetica, sans-serif;
38
+ line-height: 1.25;
39
+ }
40
+
41
+ .date {
42
+ grid-area: date;
43
+ color: var(--link-color);
44
+ text-decoration: none;
45
+ font-size: .8125rem;
46
+ }
47
+
48
+ .generationSettings {
49
+ display: grid;
50
+ grid-template-columns: 1fr 1fr;
51
  }
src/components/topics/style.module.scss CHANGED
@@ -1,77 +1,77 @@
1
- .spinner {
2
- margin: 5rem auto;
3
- }
4
-
5
- .list {
6
- display: grid;
7
- grid-template-columns: 10fr 3fr 1fr 3fr;
8
-
9
- list-style-type: none;
10
- //margin: 0;
11
- //margin-bottom: 1.5rem;
12
- background: var(--block-bg-color);
13
- border: .0625rem solid var(--border-color);
14
- border-radius: 0.75rem;
15
- width: 100%;
16
- padding: 0;
17
- overflow: hidden;
18
- margin-bottom: 1.5rem;
19
-
20
- > li {
21
- display: contents;
22
- list-style: none;
23
- padding: 0;
24
- margin: 0;
25
-
26
- > span {
27
- font-weight: var(--topic-font-weight);
28
- font-size: var(--topic-font-size);
29
- padding: var(--topic-gap);
30
-
31
- a {
32
- overflow: hidden;
33
- display: -webkit-box;
34
- -webkit-line-clamp: var(--topic-title-lines);
35
- -webkit-box-orient: vertical;
36
- //margin-left: var(--topic-gap);
37
- color: var(--link-color);
38
- text-decoration: none;
39
- font-size: var(--topic-title-font-size);
40
- font-weight: var(--topic-title-font-weight);
41
-
42
- &:hover {
43
- color: var(--text-hover-secondary);
44
- }
45
- }
46
- }
47
-
48
- &:nth-child(even) {
49
- > span {
50
- background: var(--block-even-bg-color);
51
- }
52
- }
53
-
54
- &.highlight {
55
- > span {
56
- background: var(--block-highlighted-bg-color);
57
- }
58
- }
59
- }
60
- }
61
-
62
- .head {
63
- > span {
64
- text-transform: uppercase;
65
- }
66
- }
67
-
68
- .generationSettings {
69
- max-width: 400px;
70
- //display: grid;
71
- //grid-template-columns: 1fr 0;
72
- //
73
- //
74
- //@media screen and (min-width: 600px) {
75
- // grid-template-columns: 1fr 1fr;
76
- //}
77
  }
 
1
+ .spinner {
2
+ margin: 5rem auto;
3
+ }
4
+
5
+ .list {
6
+ display: grid;
7
+ grid-template-columns: 10fr 3fr 1fr 3fr;
8
+
9
+ list-style-type: none;
10
+ //margin: 0;
11
+ //margin-bottom: 1.5rem;
12
+ background: var(--block-bg-color);
13
+ border: .0625rem solid var(--border-color);
14
+ border-radius: 0.75rem;
15
+ width: 100%;
16
+ padding: 0;
17
+ overflow: hidden;
18
+ margin-bottom: 1.5rem;
19
+
20
+ > li {
21
+ display: contents;
22
+ list-style: none;
23
+ padding: 0;
24
+ margin: 0;
25
+
26
+ > span {
27
+ font-weight: var(--topic-font-weight);
28
+ font-size: var(--topic-font-size);
29
+ padding: var(--topic-gap);
30
+
31
+ a {
32
+ overflow: hidden;
33
+ display: -webkit-box;
34
+ -webkit-line-clamp: var(--topic-title-lines);
35
+ -webkit-box-orient: vertical;
36
+ //margin-left: var(--topic-gap);
37
+ color: var(--link-color);
38
+ text-decoration: none;
39
+ font-size: var(--topic-title-font-size);
40
+ font-weight: var(--topic-title-font-weight);
41
+
42
+ &:hover {
43
+ color: var(--text-hover-secondary);
44
+ }
45
+ }
46
+ }
47
+
48
+ &:nth-child(even) {
49
+ > span {
50
+ background: var(--block-even-bg-color);
51
+ }
52
+ }
53
+
54
+ &.highlight {
55
+ > span {
56
+ background: var(--block-highlighted-bg-color);
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ .head {
63
+ > span {
64
+ text-transform: uppercase;
65
+ }
66
+ }
67
+
68
+ .generationSettings {
69
+ max-width: 400px;
70
+ //display: grid;
71
+ //grid-template-columns: 1fr 0;
72
+ //
73
+ //
74
+ //@media screen and (min-width: 600px) {
75
+ // grid-template-columns: 1fr 1fr;
76
+ //}
77
  }
src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {render, h} from "preact";
2
- import {App} from "./app";
3
-
4
  render(h(App, null), document.getElementById('app'))
 
1
+ import {render, h} from "preact";
2
+ import {App} from "./app";
3
+
4
  render(h(App, null), document.getElementById('app'))
src/reset.scss CHANGED
@@ -1,115 +1,115 @@
1
- /***
2
- The new CSS reset - version 1.11.3 (last updated 25.08.2024)
3
- GitHub page: https://github.com/elad2412/the-new-css-reset
4
- ***/
5
-
6
- /*
7
- Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
8
- - The "symbol *" part is to solve Firefox SVG sprite bug
9
- - The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36)
10
- */
11
- *:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
12
- all: unset;
13
- display: revert;
14
- }
15
-
16
- /* Preferred box-sizing value */
17
- *,
18
- *::before,
19
- *::after {
20
- box-sizing: border-box;
21
- }
22
-
23
- /* Fix mobile Safari increase font-size on landscape mode */
24
- html {
25
- -moz-text-size-adjust: none;
26
- -webkit-text-size-adjust: none;
27
- text-size-adjust: none;
28
- }
29
-
30
- /* Reapply the pointer cursor for anchor tags */
31
- a, button {
32
- cursor: revert;
33
- }
34
-
35
- /* Remove list styles (bullets/numbers) */
36
- ol, ul, menu, summary {
37
- list-style: none;
38
- }
39
-
40
- /* Firefox: solve issue where nested ordered lists continue numbering from parent (https://bugzilla.mozilla.org/show_bug.cgi?id=1881517) */
41
- ol {
42
- counter-reset: revert;
43
- }
44
-
45
- /* For images to not be able to exceed their container */
46
- img {
47
- max-inline-size: 100%;
48
- max-block-size: 100%;
49
- }
50
-
51
- /* removes spacing between cells in tables */
52
- table {
53
- border-collapse: collapse;
54
- }
55
-
56
- /* Safari - solving issue when using user-select:none on the <body> text input doesn't working */
57
- input, textarea {
58
- -webkit-user-select: auto;
59
- }
60
-
61
- /* revert the 'white-space' property for textarea elements on Safari */
62
- textarea {
63
- white-space: revert;
64
- }
65
-
66
- /* minimum style to allow to style meter element */
67
- meter {
68
- -webkit-appearance: revert;
69
- appearance: revert;
70
- }
71
-
72
- /* preformatted text - use only for this feature */
73
- :where(pre) {
74
- all: revert;
75
- box-sizing: border-box;
76
- }
77
-
78
- /* reset default text opacity of input placeholder */
79
- ::placeholder {
80
- color: unset;
81
- }
82
-
83
- /* fix the feature of 'hidden' attribute.
84
- display:revert; revert to element instead of attribute */
85
- :where([hidden]) {
86
- display: none;
87
- }
88
-
89
- /* revert for bug in Chromium browsers
90
- - fix for the content editable attribute will work properly.
91
- - webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/
92
- :where([contenteditable]:not([contenteditable="false"])) {
93
- -moz-user-modify: read-write;
94
- -webkit-user-modify: read-write;
95
- overflow-wrap: break-word;
96
- -webkit-line-break: after-white-space;
97
- -webkit-user-select: auto;
98
- }
99
-
100
- /* apply back the draggable feature - exist only in Chromium and Safari */
101
- :where([draggable="true"]) {
102
- -webkit-user-drag: element;
103
- }
104
-
105
- /* Revert Modal native behavior */
106
- //noinspection CssInvalidPseudoSelector
107
- :where(dialog:modal) {
108
- all: revert;
109
- box-sizing: border-box;
110
- }
111
-
112
- /* Remove details summary webkit styles */
113
- ::-webkit-details-marker {
114
- display: none;
115
  }
 
1
+ /***
2
+ The new CSS reset - version 1.11.3 (last updated 25.08.2024)
3
+ GitHub page: https://github.com/elad2412/the-new-css-reset
4
+ ***/
5
+
6
+ /*
7
+ Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
8
+ - The "symbol *" part is to solve Firefox SVG sprite bug
9
+ - The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36)
10
+ */
11
+ *:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
12
+ all: unset;
13
+ display: revert;
14
+ }
15
+
16
+ /* Preferred box-sizing value */
17
+ *,
18
+ *::before,
19
+ *::after {
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ /* Fix mobile Safari increase font-size on landscape mode */
24
+ html {
25
+ -moz-text-size-adjust: none;
26
+ -webkit-text-size-adjust: none;
27
+ text-size-adjust: none;
28
+ }
29
+
30
+ /* Reapply the pointer cursor for anchor tags */
31
+ a, button {
32
+ cursor: revert;
33
+ }
34
+
35
+ /* Remove list styles (bullets/numbers) */
36
+ ol, ul, menu, summary {
37
+ list-style: none;
38
+ }
39
+
40
+ /* Firefox: solve issue where nested ordered lists continue numbering from parent (https://bugzilla.mozilla.org/show_bug.cgi?id=1881517) */
41
+ ol {
42
+ counter-reset: revert;
43
+ }
44
+
45
+ /* For images to not be able to exceed their container */
46
+ img {
47
+ max-inline-size: 100%;
48
+ max-block-size: 100%;
49
+ }
50
+
51
+ /* removes spacing between cells in tables */
52
+ table {
53
+ border-collapse: collapse;
54
+ }
55
+
56
+ /* Safari - solving issue when using user-select:none on the <body> text input doesn't working */
57
+ input, textarea {
58
+ -webkit-user-select: auto;
59
+ }
60
+
61
+ /* revert the 'white-space' property for textarea elements on Safari */
62
+ textarea {
63
+ white-space: revert;
64
+ }
65
+
66
+ /* minimum style to allow to style meter element */
67
+ meter {
68
+ -webkit-appearance: revert;
69
+ appearance: revert;
70
+ }
71
+
72
+ /* preformatted text - use only for this feature */
73
+ :where(pre) {
74
+ all: revert;
75
+ box-sizing: border-box;
76
+ }
77
+
78
+ /* reset default text opacity of input placeholder */
79
+ ::placeholder {
80
+ color: unset;
81
+ }
82
+
83
+ /* fix the feature of 'hidden' attribute.
84
+ display:revert; revert to element instead of attribute */
85
+ :where([hidden]) {
86
+ display: none;
87
+ }
88
+
89
+ /* revert for bug in Chromium browsers
90
+ - fix for the content editable attribute will work properly.
91
+ - webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/
92
+ :where([contenteditable]:not([contenteditable="false"])) {
93
+ -moz-user-modify: read-write;
94
+ -webkit-user-modify: read-write;
95
+ overflow-wrap: break-word;
96
+ -webkit-line-break: after-white-space;
97
+ -webkit-user-select: auto;
98
+ }
99
+
100
+ /* apply back the draggable feature - exist only in Chromium and Safari */
101
+ :where([draggable="true"]) {
102
+ -webkit-user-drag: element;
103
+ }
104
+
105
+ /* Revert Modal native behavior */
106
+ //noinspection CssInvalidPseudoSelector
107
+ :where(dialog:modal) {
108
+ all: revert;
109
+ box-sizing: border-box;
110
+ }
111
+
112
+ /* Remove details summary webkit styles */
113
+ ::-webkit-details-marker {
114
+ display: none;
115
  }
src/style.module.scss CHANGED
@@ -1,13 +1,13 @@
1
- .header {
2
- background-color: var(--header-bg-color);
3
- color: var(--text-color);
4
- }
5
-
6
- .logo {
7
- font-size: 2rem;
8
- font-weight: 700;
9
- }
10
-
11
- .main {
12
-
13
  }
 
1
+ .header {
2
+ background-color: var(--header-bg-color);
3
+ color: var(--text-color);
4
+ }
5
+
6
+ .logo {
7
+ font-size: 2rem;
8
+ font-weight: 700;
9
+ }
10
+
11
+ .main {
12
+
13
  }
src/styles.scss CHANGED
@@ -1,139 +1,139 @@
1
- @use "./reset";
2
-
3
- @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');
4
-
5
- // JVC color theme
6
- :root {
7
- color-scheme: dark;
8
- --body-bg: #22252a;
9
- --text-primary: #3d87f5;
10
- --text-hover-primary: #5596f6;
11
- --text-secondary: #f66031;
12
- --text-hover-secondary: #f7734a;
13
- --text-danger: #e4606d;
14
- --text-hover-danger: #dc3545;
15
- --text-dark: #e0e0e0;
16
- --text-hover-dark: #fff;
17
- --text-light: #000;
18
- --bg-color-light: #272a30;
19
- --bg-color-light-transparent: rgba(39, 42, 48, 0);
20
- --bg-color: #22252a;
21
- --text-color: #f2f2f2;
22
- --border-color: #4a4c4f;
23
- --border-even-color: #454f5e;
24
- --border-highlighted-color: #765d3f;
25
- --block-bg-color: #2e3238;
26
- --block-even-bg-color: #363e49;
27
- --block-highlighted-bg-color: #594126;
28
- --block-bg-color-hover: #353941;
29
- --block-bg-color-hover-dark: #454b54;
30
- --text-muted-color: #9e9e9e;
31
- --text-muted-hover-color: #bababa;
32
- --text-blockquote-color: #c7c7d1;
33
- --link-color: #7dc3f7;
34
- --link-hover-bg-color: #4c5767;
35
- --header-bg-color: #18191b;
36
- --header-bottom-bg-color: #000;
37
- --header-bottom-bg-color-hover: #303236;
38
- --video-footer-bg-color: #1d1e20;
39
- --layout-bg-color: #292d32;
40
- --disabled-bg-color: #1d1e20;
41
- --disabled-text-color: #787e87;
42
- --disabled-border-color: #787e87;
43
- --ad-bg-color: #18191b;
44
- --ad-border-color: #000;
45
- --game-header-bg-color: #2c2f35;
46
- --bg-light: #2c2f35;
47
- --game-tab-hover-bg-color: #17191c;
48
- --price-color: #9fafc6;
49
- --blue-light: #414a58;
50
- --live-color: #ff4d4d;
51
- --live-hover-color: #ff1a1a;
52
- --gauge-bg-color: #616161;
53
- --blue-gray-color: #9fafc6;
54
- --message-delete: #104e60;
55
- --message-delete-border: #092a34;
56
- --message-delete-gta: #46435b;
57
- --message-delete-gta-border: #191820;
58
- --input-bg-color: #303236;
59
- --input-border-color: #636569;
60
- --input-text-color: #f2f2f2;
61
- --input-placeholder-color: #616161;
62
- --form-select-indicator: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath stroke=%27%23e0e0e0%27 stroke-width=%272px%27 d=%27M2 5l6 6 6-6%27 fill=%27none%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27/%3e%3c/svg%3e");
63
- --form-switch-bg-image: url("data:image/svg+xml,<svg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%27-4 -4 8 8%27><circle r=%273%27 fill=%27%23636569%27/></svg>");
64
- --admin-color: #f24444;
65
- --modo-color: #42b228;
66
- --ad-placeholder-opacity: 0.4;
67
- --alert-brightness: 0.85;
68
- --filters-bg-color: #16191d;
69
- --gray-100: #f8f9fa;
70
- --gray-200: #e9ecef;
71
- --gray-300: #dee2e6;
72
- --gray-400: #ced4da;
73
- --gray-500: #adb5bd;
74
- --gray-600: #6c757d;
75
- --gray-700: #495057;
76
- --gray-800: #343a40;
77
- --gray-900: #212529;
78
- --white-rgb: #fff;
79
- --black-rgb: #000;
80
- --body-color-rgb: var(--text-color);
81
- --body-bg-rgb: var(--body-bg);
82
- --font-sans-serif: "Roboto", Arial, Helvetica, sans-serif;
83
- --font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
84
- --gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
85
- --root-font-size: 1rem;
86
- --body-font-family: "Roboto", Arial, Helvetica, sans-serif;
87
- --body-font-size: 0.9375rem;
88
- --body-font-weight: 400;
89
- --body-line-height: 1.5;
90
- --body-color: var(--text-color);
91
- --border-width: 0.0625rem;
92
- --border-style: solid;
93
- --border-color-translucent: rgba(0, 0, 0, 0.175);
94
- --border-radius: 0.75rem;
95
- --border-radius-sm: 0.5rem;
96
- --border-radius-lg: 0.75rem;
97
- --border-radius-xl: 1rem;
98
- --border-radius-2xl: 2rem;
99
- --border-radius-pill: 50rem;
100
- --link-hover-color: var(--text-hover-secondary);
101
- --code-color: #d63384;
102
- --highlight-bg: #fff3cd;
103
- --topic-font-size: 0.8125rem;
104
- --topic-font-weight: 700;
105
- --topic-line-height: 1.25;
106
- --topic-gap: 0.625rem;
107
- --topic-title-font-size: 0.9375rem;
108
- --topic-title-font-weight: 500;
109
- }
110
-
111
- body {
112
- margin: 0;
113
- font-family: var(--body-font-family), sans-serif;
114
- font-size: var(--body-font-size);
115
- font-weight: var(--body-font-weight);
116
- line-height: var(--body-line-height);
117
- color: var(--body-color);
118
- text-align: var(--body-text-align);
119
- background-color: var(--body-bg);
120
- -webkit-text-size-adjust: 100%;
121
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
122
- }
123
-
124
- hr {
125
- //height: 0.0625rem;
126
- height: 1px;
127
- width: 100%;
128
- background-color: var(--border-color);
129
- display: block;
130
- margin: 1.5rem 0;
131
- }
132
-
133
- h2 {
134
- font-size: 1.59375rem;
135
- line-height: 1.25;
136
- font-weight: 500;
137
- color: var(--text-color);
138
- padding-bottom: 1.5rem;
139
  }
 
1
+ @use "./reset";
2
+
3
+ @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');
4
+
5
+ // JVC color theme
6
+ :root {
7
+ color-scheme: dark;
8
+ --body-bg: #22252a;
9
+ --text-primary: #3d87f5;
10
+ --text-hover-primary: #5596f6;
11
+ --text-secondary: #f66031;
12
+ --text-hover-secondary: #f7734a;
13
+ --text-danger: #e4606d;
14
+ --text-hover-danger: #dc3545;
15
+ --text-dark: #e0e0e0;
16
+ --text-hover-dark: #fff;
17
+ --text-light: #000;
18
+ --bg-color-light: #272a30;
19
+ --bg-color-light-transparent: rgba(39, 42, 48, 0);
20
+ --bg-color: #22252a;
21
+ --text-color: #f2f2f2;
22
+ --border-color: #4a4c4f;
23
+ --border-even-color: #454f5e;
24
+ --border-highlighted-color: #765d3f;
25
+ --block-bg-color: #2e3238;
26
+ --block-even-bg-color: #363e49;
27
+ --block-highlighted-bg-color: #594126;
28
+ --block-bg-color-hover: #353941;
29
+ --block-bg-color-hover-dark: #454b54;
30
+ --text-muted-color: #9e9e9e;
31
+ --text-muted-hover-color: #bababa;
32
+ --text-blockquote-color: #c7c7d1;
33
+ --link-color: #7dc3f7;
34
+ --link-hover-bg-color: #4c5767;
35
+ --header-bg-color: #18191b;
36
+ --header-bottom-bg-color: #000;
37
+ --header-bottom-bg-color-hover: #303236;
38
+ --video-footer-bg-color: #1d1e20;
39
+ --layout-bg-color: #292d32;
40
+ --disabled-bg-color: #1d1e20;
41
+ --disabled-text-color: #787e87;
42
+ --disabled-border-color: #787e87;
43
+ --ad-bg-color: #18191b;
44
+ --ad-border-color: #000;
45
+ --game-header-bg-color: #2c2f35;
46
+ --bg-light: #2c2f35;
47
+ --game-tab-hover-bg-color: #17191c;
48
+ --price-color: #9fafc6;
49
+ --blue-light: #414a58;
50
+ --live-color: #ff4d4d;
51
+ --live-hover-color: #ff1a1a;
52
+ --gauge-bg-color: #616161;
53
+ --blue-gray-color: #9fafc6;
54
+ --message-delete: #104e60;
55
+ --message-delete-border: #092a34;
56
+ --message-delete-gta: #46435b;
57
+ --message-delete-gta-border: #191820;
58
+ --input-bg-color: #303236;
59
+ --input-border-color: #636569;
60
+ --input-text-color: #f2f2f2;
61
+ --input-placeholder-color: #616161;
62
+ --form-select-indicator: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath stroke=%27%23e0e0e0%27 stroke-width=%272px%27 d=%27M2 5l6 6 6-6%27 fill=%27none%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27/%3e%3c/svg%3e");
63
+ --form-switch-bg-image: url("data:image/svg+xml,<svg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%27-4 -4 8 8%27><circle r=%273%27 fill=%27%23636569%27/></svg>");
64
+ --admin-color: #f24444;
65
+ --modo-color: #42b228;
66
+ --ad-placeholder-opacity: 0.4;
67
+ --alert-brightness: 0.85;
68
+ --filters-bg-color: #16191d;
69
+ --gray-100: #f8f9fa;
70
+ --gray-200: #e9ecef;
71
+ --gray-300: #dee2e6;
72
+ --gray-400: #ced4da;
73
+ --gray-500: #adb5bd;
74
+ --gray-600: #6c757d;
75
+ --gray-700: #495057;
76
+ --gray-800: #343a40;
77
+ --gray-900: #212529;
78
+ --white-rgb: #fff;
79
+ --black-rgb: #000;
80
+ --body-color-rgb: var(--text-color);
81
+ --body-bg-rgb: var(--body-bg);
82
+ --font-sans-serif: "Roboto", Arial, Helvetica, sans-serif;
83
+ --font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
84
+ --gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
85
+ --root-font-size: 1rem;
86
+ --body-font-family: "Roboto", Arial, Helvetica, sans-serif;
87
+ --body-font-size: 0.9375rem;
88
+ --body-font-weight: 400;
89
+ --body-line-height: 1.5;
90
+ --body-color: var(--text-color);
91
+ --border-width: 0.0625rem;
92
+ --border-style: solid;
93
+ --border-color-translucent: rgba(0, 0, 0, 0.175);
94
+ --border-radius: 0.75rem;
95
+ --border-radius-sm: 0.5rem;
96
+ --border-radius-lg: 0.75rem;
97
+ --border-radius-xl: 1rem;
98
+ --border-radius-2xl: 2rem;
99
+ --border-radius-pill: 50rem;
100
+ --link-hover-color: var(--text-hover-secondary);
101
+ --code-color: #d63384;
102
+ --highlight-bg: #fff3cd;
103
+ --topic-font-size: 0.8125rem;
104
+ --topic-font-weight: 700;
105
+ --topic-line-height: 1.25;
106
+ --topic-gap: 0.625rem;
107
+ --topic-title-font-size: 0.9375rem;
108
+ --topic-title-font-weight: 500;
109
+ }
110
+
111
+ body {
112
+ margin: 0;
113
+ font-family: var(--body-font-family), sans-serif;
114
+ font-size: var(--body-font-size);
115
+ font-weight: var(--body-font-weight);
116
+ line-height: var(--body-line-height);
117
+ color: var(--body-color);
118
+ text-align: var(--body-text-align);
119
+ background-color: var(--body-bg);
120
+ -webkit-text-size-adjust: 100%;
121
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
122
+ }
123
+
124
+ hr {
125
+ //height: 0.0625rem;
126
+ height: 1px;
127
+ width: 100%;
128
+ background-color: var(--border-color);
129
+ display: block;
130
+ margin: 1.5rem 0;
131
+ }
132
+
133
+ h2 {
134
+ font-size: 1.59375rem;
135
+ line-height: 1.25;
136
+ font-weight: 500;
137
+ color: var(--text-color);
138
+ padding-bottom: 1.5rem;
139
  }
src/utils/dates.ts CHANGED
@@ -1,51 +1,51 @@
1
- const iso8601ToFrenchRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/;
2
- export function iso8601ToFrench(iso8601: string): string {
3
- console.log("iso8601ToTokens", iso8601)
4
-
5
- const matches = iso8601.match(iso8601ToFrenchRegex);
6
-
7
- const year = matches[1];
8
- const month = months[parseInt(matches[2], 10) - 1];
9
- const day = matches[3];
10
- const hours = matches[4];
11
- const minutes = matches[5];
12
- const seconds = matches[6];
13
-
14
- return `${day} ${month} ${year} à ${hours}:${minutes}:${seconds}`;
15
- }
16
-
17
- const frenchToIso8601Regex = /(\d{1,2}) ([a-zA-Z\u00C0-\u024F]+) (\d{4}) à (\d{2}):(\d{2}):(\d{2})/;
18
- export function frenchToIso8601(french: string): string {
19
- console.log("tokensToIso8601", french)
20
- // Match the components of the date and time from the input string
21
-
22
- // const months = {
23
- // janvier: '01', février: '02', mars: '03', avril: '04', mai: '05',
24
- // juin: '06', juillet: '07', août: '08', septembre: '09',
25
- // octobre: '10', novembre: '11', décembre: '12'
26
- // };
27
-
28
- const match = french.match(frenchToIso8601Regex);
29
-
30
- if (!match) {
31
- throw new Error('Invalid date format');
32
- }
33
-
34
- const [, day, month, year, hours, minutes, seconds] = match;
35
-
36
- // Convert the month name to a numeric format
37
- const monthNumber = (months.indexOf(month) + 1).toString();
38
-
39
- if (!monthNumber) {
40
- throw new Error('Invalid month name');
41
- }
42
-
43
- // Construct the ISO 8601 formatted string
44
- const isoDate = `${year}-${monthNumber.padStart(2, '0')}-${day.padStart(2, '0')}T${hours}:${minutes}:${seconds}`;
45
- return isoDate;
46
- }
47
-
48
- const months = [
49
- "janvier", "février", "mars", "avril", "mai", "juin",
50
- "juillet", "août", "septembre", "octobre", "novembre", "décembre"
51
  ];
 
1
+ const iso8601ToFrenchRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/;
2
+ export function iso8601ToFrench(iso8601: string): string {
3
+ console.log("iso8601ToTokens", iso8601)
4
+
5
+ const matches = iso8601.match(iso8601ToFrenchRegex);
6
+
7
+ const year = matches[1];
8
+ const month = months[parseInt(matches[2], 10) - 1];
9
+ const day = matches[3];
10
+ const hours = matches[4];
11
+ const minutes = matches[5];
12
+ const seconds = matches[6];
13
+
14
+ return `${day} ${month} ${year} à ${hours}:${minutes}:${seconds}`;
15
+ }
16
+
17
+ const frenchToIso8601Regex = /(\d{1,2}) ([a-zA-Z\u00C0-\u024F]+) (\d{4}) à (\d{2}):(\d{2}):(\d{2})/;
18
+ export function frenchToIso8601(french: string): string {
19
+ console.log("tokensToIso8601", french)
20
+ // Match the components of the date and time from the input string
21
+
22
+ // const months = {
23
+ // janvier: '01', février: '02', mars: '03', avril: '04', mai: '05',
24
+ // juin: '06', juillet: '07', août: '08', septembre: '09',
25
+ // octobre: '10', novembre: '11', décembre: '12'
26
+ // };
27
+
28
+ const match = french.match(frenchToIso8601Regex);
29
+
30
+ if (!match) {
31
+ throw new Error('Invalid date format');
32
+ }
33
+
34
+ const [, day, month, year, hours, minutes, seconds] = match;
35
+
36
+ // Convert the month name to a numeric format
37
+ const monthNumber = (months.indexOf(month) + 1).toString();
38
+
39
+ if (!monthNumber) {
40
+ throw new Error('Invalid month name');
41
+ }
42
+
43
+ // Construct the ISO 8601 formatted string
44
+ const isoDate = `${year}-${monthNumber.padStart(2, '0')}-${day.padStart(2, '0')}T${hours}:${minutes}:${seconds}`;
45
+ return isoDate;
46
+ }
47
+
48
+ const months = [
49
+ "janvier", "février", "mars", "avril", "mai", "juin",
50
+ "juillet", "août", "septembre", "octobre", "novembre", "décembre"
51
  ];
src/utils/model.ts CHANGED
@@ -1,107 +1,107 @@
1
- import {Post, Topic} from "./topics";
2
- import {iso8601ToFrench, frenchToIso8601} from "./dates"
3
- import {generateUUID} from "./uuids";
4
-
5
- // const titleRegex = /Sujet\s+:\s+"([^"]+)"/;
6
- const titleRegex = /Sujet\s+:\s+"(.+?)"?<\|eot_id\|>/;
7
- const userRegex = /<\|im_pseudo\|>([^<]+)<\|end_pseudo\|>/;
8
- const dateRegex = /<\|im_date\|>([^<]+)<\|end_date\|>/;
9
- const contentRegex = /<\|begin_of_post\|>([\s\S]+)(?:<\|end_of_post\|>)?$/;
10
-
11
- export function tokensToTopic(tokens: string): Topic {
12
- const topic: Topic = {
13
- id: generateUUID(),
14
- title: "",
15
- posts: [],
16
- };
17
-
18
- // const splits = tokens.split("<|end_of_post|>")
19
- // console.log("Splits:")
20
- // console.log(splits);
21
-
22
- // Split token in posts
23
- // The last element is always vois, so remove it
24
- for(const postTokens of tokens.split("<|end_of_post|>").slice(0, -1)) {
25
- console.log("Post tokens:")
26
- console.log(postTokens);
27
-
28
- // If it's the first post
29
- if(topic.posts.length < 1) {
30
- const titleMatch = postTokens.match(titleRegex);
31
- console.log(`title: ${titleMatch[1]}`)
32
-
33
- topic.title = titleMatch[1];
34
- }
35
-
36
- // topic.posts.push(tokensToPosts());
37
- topic.posts = topic.posts.concat(tokensToPosts(postTokens));
38
- }
39
-
40
- return topic;
41
- }
42
-
43
- export function tokensToPosts(tokens: string): Post[] {
44
- const posts: Post[] = [];
45
-
46
- for(const postTokens of tokens.split("<|end_of_post|>")) {
47
-
48
- // TODO: remove the last instead of doing this, because the last can be incomplete
49
- if(postTokens.length < 1) {
50
- continue;
51
- }
52
-
53
- console.log("Post tokens:")
54
- console.log(postTokens);
55
-
56
- const userMatch = postTokens.match(userRegex);
57
- console.log(`user: ${userMatch[1]}`)
58
-
59
- const dateMatch = postTokens.match(dateRegex);
60
- console.log(`date: ${dateMatch[1]}`)
61
-
62
- const contentMatch = postTokens.match(contentRegex);
63
- console.log(`content: ${contentMatch[1]}`)
64
-
65
- posts.push({
66
- user: userMatch[1],
67
- date: frenchToIso8601(dateMatch[1]),
68
- content: contentMatch[1],
69
- });
70
- }
71
-
72
- return posts;
73
- }
74
-
75
-
76
- export function tokenizeTopic(topic: Topic): string {
77
- if (topic.posts.length === 0) {
78
- throw new Error("Topic must have at least one post")
79
- }
80
-
81
- const tokenizedPosts = topic.posts.map(post => tokenizePost(post, topic.posts[0].user)).flat().join("");
82
-
83
- // console.log("Tokenized posts:")
84
- // console.log(tokenizedPosts)
85
-
86
- let lines = [
87
- "<|start_header_id|><|sujet|><|end_header_id|>",
88
- "",
89
- `Sujet : "${topic.title}"`,
90
- ];
91
-
92
- return lines.join("\n") + tokenizedPosts;
93
- }
94
-
95
- export function tokenizePost(post: Post, poster: string): string {
96
- let lines = [
97
- `<|eot_id|><|start_header_id|><|${post.user === poster ? "autheur" : "khey"}|>`,
98
- "<|end_header_id|>",
99
- "",
100
- `<|im_pseudo|>${post.user}<|end_pseudo|>`,
101
- `<|im_date|>Le ${iso8601ToFrench(post.date)}<|end_date|>`,
102
- "",
103
- `<|begin_of_post|>${post.content}<|end_of_post|>`
104
- ];
105
-
106
- return lines.join("\n");
107
  }
 
1
+ import {Post, Topic} from "./topics";
2
+ import {iso8601ToFrench, frenchToIso8601} from "./dates"
3
+ import {generateUUID} from "./uuids";
4
+
5
+ // const titleRegex = /Sujet\s+:\s+"([^"]+)"/;
6
+ const titleRegex = /Sujet\s+:\s+"(.+?)"?<\|eot_id\|>/;
7
+ const userRegex = /<\|im_pseudo\|>([^<]+)<\|end_pseudo\|>/;
8
+ const dateRegex = /<\|im_date\|>([^<]+)<\|end_date\|>/;
9
+ const contentRegex = /<\|begin_of_post\|>([\s\S]+)(?:<\|end_of_post\|>)?$/;
10
+
11
+ export function tokensToTopic(tokens: string): Topic {
12
+ const topic: Topic = {
13
+ id: generateUUID(),
14
+ title: "",
15
+ posts: [],
16
+ };
17
+
18
+ // const splits = tokens.split("<|end_of_post|>")
19
+ // console.log("Splits:")
20
+ // console.log(splits);
21
+
22
+ // Split token in posts
23
+ // The last element is always vois, so remove it
24
+ for(const postTokens of tokens.split("<|end_of_post|>").slice(0, -1)) {
25
+ console.log("Post tokens:")
26
+ console.log(postTokens);
27
+
28
+ // If it's the first post
29
+ if(topic.posts.length < 1) {
30
+ const titleMatch = postTokens.match(titleRegex);
31
+ console.log(`title: ${titleMatch[1]}`)
32
+
33
+ topic.title = titleMatch[1];
34
+ }
35
+
36
+ // topic.posts.push(tokensToPosts());
37
+ topic.posts = topic.posts.concat(tokensToPosts(postTokens));
38
+ }
39
+
40
+ return topic;
41
+ }
42
+
43
+ export function tokensToPosts(tokens: string): Post[] {
44
+ const posts: Post[] = [];
45
+
46
+ for(const postTokens of tokens.split("<|end_of_post|>")) {
47
+
48
+ // TODO: remove the last instead of doing this, because the last can be incomplete
49
+ if(postTokens.length < 1) {
50
+ continue;
51
+ }
52
+
53
+ console.log("Post tokens:")
54
+ console.log(postTokens);
55
+
56
+ const userMatch = postTokens.match(userRegex);
57
+ console.log(`user: ${userMatch[1]}`)
58
+
59
+ const dateMatch = postTokens.match(dateRegex);
60
+ console.log(`date: ${dateMatch[1]}`)
61
+
62
+ const contentMatch = postTokens.match(contentRegex);
63
+ console.log(`content: ${contentMatch[1]}`)
64
+
65
+ posts.push({
66
+ user: userMatch[1],
67
+ date: frenchToIso8601(dateMatch[1]),
68
+ content: contentMatch[1],
69
+ });
70
+ }
71
+
72
+ return posts;
73
+ }
74
+
75
+
76
+ export function tokenizeTopic(topic: Topic): string {
77
+ if (topic.posts.length === 0) {
78
+ throw new Error("Topic must have at least one post")
79
+ }
80
+
81
+ const tokenizedPosts = topic.posts.map(post => tokenizePost(post, topic.posts[0].user)).flat().join("");
82
+
83
+ // console.log("Tokenized posts:")
84
+ // console.log(tokenizedPosts)
85
+
86
+ let lines = [
87
+ "<|start_header_id|><|sujet|><|end_header_id|>",
88
+ "",
89
+ `Sujet : "${topic.title}"`,
90
+ ];
91
+
92
+ return lines.join("\n") + tokenizedPosts;
93
+ }
94
+
95
+ export function tokenizePost(post: Post, poster: string): string {
96
+ let lines = [
97
+ `<|eot_id|><|start_header_id|><|${post.user === poster ? "autheur" : "khey"}|>`,
98
+ "<|end_header_id|>",
99
+ "",
100
+ `<|im_pseudo|>${post.user}<|end_pseudo|>`,
101
+ `<|im_date|>Le ${iso8601ToFrench(post.date)}<|end_date|>`,
102
+ "",
103
+ `<|begin_of_post|>${post.content}<|end_of_post|>`
104
+ ];
105
+
106
+ return lines.join("\n");
107
  }
src/utils/route.ts CHANGED
@@ -1,9 +1,9 @@
1
- export const routes = {
2
- home: "home",
3
- topic: "topic",
4
- settings: "settings",
5
- } as const;
6
-
7
- export type Route = typeof routes[keyof typeof routes];
8
-
9
  export type RouteSetter = (route: Route, page?: number, id?: string) => void;
 
1
+ export const routes = {
2
+ home: "home",
3
+ topic: "topic",
4
+ settings: "settings",
5
+ } as const;
6
+
7
+ export type Route = typeof routes[keyof typeof routes];
8
+
9
  export type RouteSetter = (route: Route, page?: number, id?: string) => void;
src/utils/settings.ts CHANGED
@@ -1,30 +1,30 @@
1
- const itemKey = "settings";
2
-
3
- export type Settings = {
4
- apiURL: string;
5
- temperature: number;
6
- postCount: number;
7
- }
8
-
9
- const defaultSettings: Settings = {
10
- apiURL: "http://localhost:5000",
11
- temperature: 0.9,
12
- postCount: 3,
13
- }
14
-
15
- export function fetchSettings(): Settings {
16
- const storedSettings = localStorage.getItem(itemKey);
17
- if (storedSettings) {
18
- return {...defaultSettings, ...JSON.parse(storedSettings)} as Settings;
19
- } else {
20
- return defaultSettings;
21
- }
22
- }
23
-
24
- export function saveSettings(settings: Settings) {
25
- localStorage.setItem(itemKey, JSON.stringify(settings));
26
- }
27
-
28
- export function resetSettings() {
29
- localStorage.removeItem(itemKey);
30
  }
 
1
+ const itemKey = "settings";
2
+
3
+ export type Settings = {
4
+ apiURL: string;
5
+ temperature: number;
6
+ postCount: number;
7
+ }
8
+
9
+ const defaultSettings: Settings = {
10
+ apiURL: "http://localhost:5000",
11
+ temperature: 0.9,
12
+ postCount: 3,
13
+ }
14
+
15
+ export function fetchSettings(): Settings {
16
+ const storedSettings = localStorage.getItem(itemKey);
17
+ if (storedSettings) {
18
+ return {...defaultSettings, ...JSON.parse(storedSettings)} as Settings;
19
+ } else {
20
+ return defaultSettings;
21
+ }
22
+ }
23
+
24
+ export function saveSettings(settings: Settings) {
25
+ localStorage.setItem(itemKey, JSON.stringify(settings));
26
+ }
27
+
28
+ export function resetSettings() {
29
+ localStorage.removeItem(itemKey);
30
  }
src/utils/smileys.ts CHANGED
@@ -1,79 +1,79 @@
1
- export const smileysMap: [string, string][] = [
2
- [":)", "1"], // https://image.jeuxvideo.com/smileys_img/1.gif
3
- [":snif:", "20"],
4
- [":gba:", "17"],
5
- [":g)", "3"],
6
- [":-)", "46"],
7
- [":snif2:", "13"],
8
- [":bravo:", "69"],
9
- [":d)", "4"],
10
- [":hap:", "18"],
11
- [":ouch:", "22"],
12
- [":pacg:", "9"],
13
- [":cd:", "5"],
14
- [":-)))", "23"],
15
- [":ouch2:", "57"],
16
- [":pacd:", "10"],
17
- [":cute:", "nyu"],
18
- [":content:", "24"],
19
- [":p)", "7"],
20
- [":-p", "31"],
21
- [":noel:", "11"],
22
- [":oui:", "37"],
23
- [":(", "45"],
24
- [":peur:", "47"],
25
- [":question:", "2"],
26
- [":cool:", "26"],
27
- [":-(", "14"],
28
- [":coeur:", "54"],
29
- [":mort:", "21"],
30
- [":rire:", "39"],
31
- [":-((", "15"],
32
- [":fou:", "50"],
33
- [":sleep:", "27"],
34
- [":-D", "40"],
35
- [":nonnon:", "25"],
36
- [":fier:", "53"],
37
- [":honte:", "30"],
38
- [":rire2:", "41"],
39
- [":non2:", "33"],
40
- [":sarcastic:", "43"],
41
- [":monoeil:", "34"],
42
- [":o))", "12"],
43
- [":nah:", "19"],
44
- [":doute:", "28"],
45
- [":rouge:", "55"],
46
- [":ok:", "36"],
47
- [":non:", "35"],
48
- [":malade:", "8"],
49
- [":fete:", "66"],
50
- [":sournois:", "67"],
51
- [":hum:", "68"],
52
- [":ange:", "60"],
53
- [":diable:", "61"],
54
- [":gni:", "62"],
55
- [":play:", "play"],
56
- [":desole:", "65"],
57
- [":spoiler:", "63"],
58
- [":merci:", "58"],
59
- [":svp:", "59"],
60
- [":sors:", "56"],
61
- [":salut:", "42"],
62
- [":rechercher:", "38"],
63
- [":hello:", "29"],
64
- [":up:", "44"],
65
- [":bye:", "48"],
66
- [":gne:", "51"],
67
- [":lol:", "32"],
68
- [":dpdr:", "49"],
69
- [":dehors:", "52"],
70
- [":hs:", "64"],
71
- [":banzai:", "70"],
72
- [":bave:", "71"],
73
- [":pf:", "pf"],
74
- [":cimer:", "cimer"],
75
- [":ddb:", "ddb"],
76
- [":pave:", "pave"],
77
- [":objection:", "objection"],
78
- [":siffle:", "siffle"],
79
  ];
 
1
+ export const smileysMap: [string, string][] = [
2
+ [":)", "1"], // https://image.jeuxvideo.com/smileys_img/1.gif
3
+ [":snif:", "20"],
4
+ [":gba:", "17"],
5
+ [":g)", "3"],
6
+ [":-)", "46"],
7
+ [":snif2:", "13"],
8
+ [":bravo:", "69"],
9
+ [":d)", "4"],
10
+ [":hap:", "18"],
11
+ [":ouch:", "22"],
12
+ [":pacg:", "9"],
13
+ [":cd:", "5"],
14
+ [":-)))", "23"],
15
+ [":ouch2:", "57"],
16
+ [":pacd:", "10"],
17
+ [":cute:", "nyu"],
18
+ [":content:", "24"],
19
+ [":p)", "7"],
20
+ [":-p", "31"],
21
+ [":noel:", "11"],
22
+ [":oui:", "37"],
23
+ [":(", "45"],
24
+ [":peur:", "47"],
25
+ [":question:", "2"],
26
+ [":cool:", "26"],
27
+ [":-(", "14"],
28
+ [":coeur:", "54"],
29
+ [":mort:", "21"],
30
+ [":rire:", "39"],
31
+ [":-((", "15"],
32
+ [":fou:", "50"],
33
+ [":sleep:", "27"],
34
+ [":-D", "40"],
35
+ [":nonnon:", "25"],
36
+ [":fier:", "53"],
37
+ [":honte:", "30"],
38
+ [":rire2:", "41"],
39
+ [":non2:", "33"],
40
+ [":sarcastic:", "43"],
41
+ [":monoeil:", "34"],
42
+ [":o))", "12"],
43
+ [":nah:", "19"],
44
+ [":doute:", "28"],
45
+ [":rouge:", "55"],
46
+ [":ok:", "36"],
47
+ [":non:", "35"],
48
+ [":malade:", "8"],
49
+ [":fete:", "66"],
50
+ [":sournois:", "67"],
51
+ [":hum:", "68"],
52
+ [":ange:", "60"],
53
+ [":diable:", "61"],
54
+ [":gni:", "62"],
55
+ [":play:", "play"],
56
+ [":desole:", "65"],
57
+ [":spoiler:", "63"],
58
+ [":merci:", "58"],
59
+ [":svp:", "59"],
60
+ [":sors:", "56"],
61
+ [":salut:", "42"],
62
+ [":rechercher:", "38"],
63
+ [":hello:", "29"],
64
+ [":up:", "44"],
65
+ [":bye:", "48"],
66
+ [":gne:", "51"],
67
+ [":lol:", "32"],
68
+ [":dpdr:", "49"],
69
+ [":dehors:", "52"],
70
+ [":hs:", "64"],
71
+ [":banzai:", "70"],
72
+ [":bave:", "71"],
73
+ [":pf:", "pf"],
74
+ [":cimer:", "cimer"],
75
+ [":ddb:", "ddb"],
76
+ [":pave:", "pave"],
77
+ [":objection:", "objection"],
78
+ [":siffle:", "siffle"],
79
  ];
src/utils/topics.ts CHANGED
@@ -1,135 +1,135 @@
1
- export type Post = {
2
- user: string;
3
- date: string; // ISO 8601, YYYY-MM-DDTHH:MM:SS
4
- content: string;
5
- }
6
-
7
- export type Topic = {
8
- id: string; // UUID
9
- title: string;
10
- posts: Post[];
11
- }
12
-
13
- const itemKey = "topics";
14
-
15
- export function loadTopics(): Topic[] {
16
- const storedTopics = localStorage.getItem(itemKey);
17
- if (storedTopics) {
18
- return JSON.parse(storedTopics) as Topic[];
19
- } else {
20
- return [];
21
- }
22
- }
23
-
24
- export function saveTopics(topics: Topic[]) {
25
- localStorage.setItem(itemKey, JSON.stringify(topics));
26
- }
27
-
28
-
29
- const testTopics: Topic[] = [
30
- {
31
- id: "dbf575b3-fc93-47b8-8773-bd91692dc97c",
32
- title: "Les néerlandais font tous 2m",
33
- posts: [
34
- {
35
- user: "elegante12",
36
- date: "2025-01-15T09:13:44",
37
- content: "https://vocaroo.com/1cCsqH5ai8AW\nN'allais pas ma bas si vous faites pas mini 1m80 même les meufs sont dans les 1m75 minimum"
38
- },
39
- {
40
- user: "Pajoad",
41
- date: "2025-01-15T09:14:45",
42
- content: "Je suis (oui c'est on EST notre taille) 1m73 et j'ai été à Amsterdam en 2022, j'ai eu l'impression d'avoir 10 ans"
43
- },
44
- {
45
- user: "Qui-es-tu",
46
- date: "2025-01-15T09:15:13",
47
- content: "Un enfer sur terre quoi."
48
- },
49
- ]
50
- },
51
- {
52
- id: "0dbfad91-c5b7-4e11-a41f-eae8162ddfa5",
53
- title: "C'est normal qu'une fille pète au lit ?",
54
- posts: [
55
- {
56
- user: "Muskis",
57
- date: "2025-01-15T10:47:14",
58
- content: "Quand on couche avec une fille c'est normal qu'elle pète au lit avant ou après l'amour en mode balécouilles que tu sois là ?"
59
- },
60
- {
61
- user: "edreMaLeuPcvJ-4",
62
- date: "2025-01-15T10:49:45",
63
- content: "Non, les filles ne pètent pas.\nSi elle pète c'est qu'elle est trans :ok:"
64
- },
65
- {
66
- user: "PereAttali",
67
- date: "2025-01-15T10:49:37",
68
- content: "Quand tu lui fous dans le fion et que ta queue est trop large c'est ce qui peut arriver (lié à la dilatation rapide)"
69
- },
70
- {
71
- user: "Pajoad",
72
- date: "2025-01-15T10:56:37",
73
- content: "Ça a quelle odeur d'ailleurs un pet féminin ?"
74
- },
75
- {
76
- user: "hypertendu",
77
- date: "2025-01-15T10:58:15",
78
- content: "Ça peut être un pet vaginal :("
79
- },
80
- ]
81
- },
82
- {
83
- id: "816dbd52-3f8e-4e44-9b8a-fcc388485e9f",
84
- title: "Ta cousine veut plaquer ses FEETS sur ton visage",
85
- posts: [
86
- {
87
- user: "BanDef411",
88
- date: "2025-01-15T10:42:00",
89
- content: "Es-tu préparé face a l’odeur? :("
90
- },
91
- {
92
- user: "McLoviin",
93
- date: "2025-01-15T10:43:35",
94
- content: "Etant donné qu'elle me fait bander comme un taureau depuis l'adolescence je suis son tapis, son fauteuil, tout est dispo pour elle."
95
- },
96
- ]
97
- },
98
- {
99
- id: "e1504b59-3918-4ee6-bced-efcd3af3f278",
100
- title: "[CDD] Je BAISE ta FEMME contre REMUNERATION",
101
- posts: [
102
- {
103
- user: "LeFeetBossV3",
104
- date: "2025-01-15T11:04:30",
105
- content: "Vu qu'apparement tu sais pas la satisfaire, pour 500 balles je la baise vite fait https://image.noelshack.com/minis/2018/25/2/1529422413-risitaszoom.png\n\nElle ira mieux et votre couple sera sauvé https://image.noelshack.com/minis/2018/25/2/1529422413-risitaszoom.png"
106
- },
107
- {
108
- user: "Platitude24",
109
- date: "2025-01-15T11:05:51",
110
- content: "Ma femme possède un pénis féminin et elle demande si elle peut te sodomiser ?"
111
- },
112
- ]
113
- },
114
- {
115
- id: "e5c288db-45a0-43e0-aa37-df485da475e7",
116
- title: "La policière au gros CUL de Marseille est devenu MILLIONNAIRE",
117
- posts: [
118
- {
119
- user: "Samanthabagare3",
120
- date: "2025-01-15T11:59:32",
121
- content: "https://image.noelshack.com/fichiers/2025/03/3/1736938679-screenshot-2025-01-15-11-57-10-521-com-instagram-android-edit.jpg\nhttps://image.noelshack.com/minis/2025/03/3/1736938679-screenshot-2025-01-15-11-57-10-521-com-instagram-android-edit.png\nEntrepreneuriat\" = Mym + onlyfan https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png\n\nFrom 500 abonnés to 232 000 grâce au buzz https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png"
122
- },
123
- {
124
- user: "Platitude24",
125
- date: "2025-01-15T11:59:36",
126
- content: "Comment ça peut être policière ça ? :("
127
- },
128
- {
129
- user: "Samanthabagare3",
130
- date: "2025-01-15T12:00:02",
131
- content: "> Le 15 janvier 2025 à 11:59:36 :\n> Comment ça peut être policière ça ?\n\nAHI <spoil>loool</spoil> AHI\nShitholisation du pays https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png"
132
- },
133
- ]
134
- },
135
  ];
 
1
+ export type Post = {
2
+ user: string;
3
+ date: string; // ISO 8601, YYYY-MM-DDTHH:MM:SS
4
+ content: string;
5
+ }
6
+
7
+ export type Topic = {
8
+ id: string; // UUID
9
+ title: string;
10
+ posts: Post[];
11
+ }
12
+
13
+ const itemKey = "topics";
14
+
15
+ export function loadTopics(): Topic[] {
16
+ const storedTopics = localStorage.getItem(itemKey);
17
+ if (storedTopics) {
18
+ return JSON.parse(storedTopics) as Topic[];
19
+ } else {
20
+ return [];
21
+ }
22
+ }
23
+
24
+ export function saveTopics(topics: Topic[]) {
25
+ localStorage.setItem(itemKey, JSON.stringify(topics));
26
+ }
27
+
28
+
29
+ const testTopics: Topic[] = [
30
+ {
31
+ id: "dbf575b3-fc93-47b8-8773-bd91692dc97c",
32
+ title: "Les néerlandais font tous 2m",
33
+ posts: [
34
+ {
35
+ user: "elegante12",
36
+ date: "2025-01-15T09:13:44",
37
+ content: "https://vocaroo.com/1cCsqH5ai8AW\nN'allais pas ma bas si vous faites pas mini 1m80 même les meufs sont dans les 1m75 minimum"
38
+ },
39
+ {
40
+ user: "Pajoad",
41
+ date: "2025-01-15T09:14:45",
42
+ content: "Je suis (oui c'est on EST notre taille) 1m73 et j'ai été à Amsterdam en 2022, j'ai eu l'impression d'avoir 10 ans"
43
+ },
44
+ {
45
+ user: "Qui-es-tu",
46
+ date: "2025-01-15T09:15:13",
47
+ content: "Un enfer sur terre quoi."
48
+ },
49
+ ]
50
+ },
51
+ {
52
+ id: "0dbfad91-c5b7-4e11-a41f-eae8162ddfa5",
53
+ title: "C'est normal qu'une fille pète au lit ?",
54
+ posts: [
55
+ {
56
+ user: "Muskis",
57
+ date: "2025-01-15T10:47:14",
58
+ content: "Quand on couche avec une fille c'est normal qu'elle pète au lit avant ou après l'amour en mode balécouilles que tu sois là ?"
59
+ },
60
+ {
61
+ user: "edreMaLeuPcvJ-4",
62
+ date: "2025-01-15T10:49:45",
63
+ content: "Non, les filles ne pètent pas.\nSi elle pète c'est qu'elle est trans :ok:"
64
+ },
65
+ {
66
+ user: "PereAttali",
67
+ date: "2025-01-15T10:49:37",
68
+ content: "Quand tu lui fous dans le fion et que ta queue est trop large c'est ce qui peut arriver (lié à la dilatation rapide)"
69
+ },
70
+ {
71
+ user: "Pajoad",
72
+ date: "2025-01-15T10:56:37",
73
+ content: "Ça a quelle odeur d'ailleurs un pet féminin ?"
74
+ },
75
+ {
76
+ user: "hypertendu",
77
+ date: "2025-01-15T10:58:15",
78
+ content: "Ça peut être un pet vaginal :("
79
+ },
80
+ ]
81
+ },
82
+ {
83
+ id: "816dbd52-3f8e-4e44-9b8a-fcc388485e9f",
84
+ title: "Ta cousine veut plaquer ses FEETS sur ton visage",
85
+ posts: [
86
+ {
87
+ user: "BanDef411",
88
+ date: "2025-01-15T10:42:00",
89
+ content: "Es-tu préparé face a l’odeur? :("
90
+ },
91
+ {
92
+ user: "McLoviin",
93
+ date: "2025-01-15T10:43:35",
94
+ content: "Etant donné qu'elle me fait bander comme un taureau depuis l'adolescence je suis son tapis, son fauteuil, tout est dispo pour elle."
95
+ },
96
+ ]
97
+ },
98
+ {
99
+ id: "e1504b59-3918-4ee6-bced-efcd3af3f278",
100
+ title: "[CDD] Je BAISE ta FEMME contre REMUNERATION",
101
+ posts: [
102
+ {
103
+ user: "LeFeetBossV3",
104
+ date: "2025-01-15T11:04:30",
105
+ content: "Vu qu'apparement tu sais pas la satisfaire, pour 500 balles je la baise vite fait https://image.noelshack.com/minis/2018/25/2/1529422413-risitaszoom.png\n\nElle ira mieux et votre couple sera sauvé https://image.noelshack.com/minis/2018/25/2/1529422413-risitaszoom.png"
106
+ },
107
+ {
108
+ user: "Platitude24",
109
+ date: "2025-01-15T11:05:51",
110
+ content: "Ma femme possède un pénis féminin et elle demande si elle peut te sodomiser ?"
111
+ },
112
+ ]
113
+ },
114
+ {
115
+ id: "e5c288db-45a0-43e0-aa37-df485da475e7",
116
+ title: "La policière au gros CUL de Marseille est devenu MILLIONNAIRE",
117
+ posts: [
118
+ {
119
+ user: "Samanthabagare3",
120
+ date: "2025-01-15T11:59:32",
121
+ content: "https://image.noelshack.com/fichiers/2025/03/3/1736938679-screenshot-2025-01-15-11-57-10-521-com-instagram-android-edit.jpg\nhttps://image.noelshack.com/minis/2025/03/3/1736938679-screenshot-2025-01-15-11-57-10-521-com-instagram-android-edit.png\nEntrepreneuriat\" = Mym + onlyfan https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png\n\nFrom 500 abonnés to 232 000 grâce au buzz https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png"
122
+ },
123
+ {
124
+ user: "Platitude24",
125
+ date: "2025-01-15T11:59:36",
126
+ content: "Comment ça peut être policière ça ? :("
127
+ },
128
+ {
129
+ user: "Samanthabagare3",
130
+ date: "2025-01-15T12:00:02",
131
+ content: "> Le 15 janvier 2025 à 11:59:36 :\n> Comment ça peut être policière ça ?\n\nAHI <spoil>loool</spoil> AHI\nShitholisation du pays https://image.noelshack.com/fichiers/2018/25/2/1529422413-risitaszoom.png"
132
+ },
133
+ ]
134
+ },
135
  ];
src/utils/uuids.ts CHANGED
@@ -1,5 +1,5 @@
1
- export function generateUUID(): string {
2
- return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
3
- (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
4
- );
5
  }
 
1
+ export function generateUUID(): string {
2
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
3
+ (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
4
+ );
5
  }