39 | {{-- Container for progress area --}}
40 |
41 | {{-- Progress block --}}
42 |
56 |
57 |
58 | @endforeach
59 |
60 | @endsection
61 |
--------------------------------------------------------------------------------
/app/Http/Controllers/V1/UploadController.php:
--------------------------------------------------------------------------------
1 | rememeberToken = $token; // Assign the generated token to rememberToken (typo: should be 'rememberToken')
27 | $user->save(); // Save the user instance to the database
28 | return Cookie::queue('user', $token, $minutes); // Set the 'user' cookie with the generated token
29 | }
30 |
31 | // User cookie is set, retrieve user's links
32 | $user = User::where("rememeberToken", Cookie::get("user"))->first(); // Find the user by rememberToken
33 | $links = $user->links; // Get all links associated with the user
34 | return view('index', compact('links')); // Return the 'index' view with links data
35 | }
36 |
37 | public function upload(StoreFileRequest $request)
38 | {
39 | $telePorter = new TelePorter(env('TELEGRAM_BOT_TOKEN'), env('CHAT_ID')); // Instantiate TelePorter with Telegram bot token and chat ID
40 |
41 | if ($request->hasFile("file")) { // Check if file is uploaded
42 | $fileName = time() . '.' . $request->file->extension(); // Generate a unique filename based on current time and file extension
43 | $request->file->move(public_path('file'), $fileName); // Move uploaded file to 'public/file' directory
44 |
45 | $url = URL::to("/") . "/file/" . $fileName; // Construct URL to access the uploaded file
46 | $fileUrl = $telePorter->upload($url); // Upload the file to Telegram using TelePorter and get the Telegram URL
47 |
48 | $user = User::where("rememeberToken", Cookie::get("user"))->first(); // Find the user by rememberToken
49 | $link = new Link(); // Create a new Link instance
50 | $link->telegram_url = $fileUrl; // Assign the Telegram URL of the uploaded file
51 | $link->file_name = $request->file->getClientOriginalName(); // Get the original filename of the uploaded file
52 | $link->user_id = $user->id; // Assign the user ID to the link
53 | $link->private_url = Carbon::now()->format("Y/m/d/l/H/i/s/") . $fileName; // Generate a private URL based on current timestamp and filename
54 | $link->save(); // Save the link instance to the database
55 |
56 | unlink(public_path("file/" . $fileName)); // Delete the temporary file from 'public/file' directory
57 | }
58 |
59 | return redirect()->back()->with("success", 'با موفقیت اپلود شد!'); // Redirect back with success message
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_DRIVER', 'file'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Stores
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the cache "stores" for your application as
26 | | well as their drivers. You may even define multiple stores for the
27 | | same cache driver to group types of items stored in your caches.
28 | |
29 | | Supported drivers: "apc", "array", "database", "file",
30 | | "memcached", "redis", "dynamodb", "octane", "null"
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'apc' => [
37 | 'driver' => 'apc',
38 | ],
39 |
40 | 'array' => [
41 | 'driver' => 'array',
42 | 'serialize' => false,
43 | ],
44 |
45 | 'database' => [
46 | 'driver' => 'database',
47 | 'table' => 'cache',
48 | 'connection' => null,
49 | 'lock_connection' => null,
50 | ],
51 |
52 | 'file' => [
53 | 'driver' => 'file',
54 | 'path' => storage_path('framework/cache/data'),
55 | ],
56 |
57 | 'memcached' => [
58 | 'driver' => 'memcached',
59 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
60 | 'sasl' => [
61 | env('MEMCACHED_USERNAME'),
62 | env('MEMCACHED_PASSWORD'),
63 | ],
64 | 'options' => [
65 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
66 | ],
67 | 'servers' => [
68 | [
69 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
70 | 'port' => env('MEMCACHED_PORT', 11211),
71 | 'weight' => 100,
72 | ],
73 | ],
74 | ],
75 |
76 | 'redis' => [
77 | 'driver' => 'redis',
78 | 'connection' => 'cache',
79 | 'lock_connection' => 'default',
80 | ],
81 |
82 | 'dynamodb' => [
83 | 'driver' => 'dynamodb',
84 | 'key' => env('AWS_ACCESS_KEY_ID'),
85 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
86 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
87 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
88 | 'endpoint' => env('DYNAMODB_ENDPOINT'),
89 | ],
90 |
91 | 'octane' => [
92 | 'driver' => 'octane',
93 | ],
94 |
95 | ],
96 |
97 | /*
98 | |--------------------------------------------------------------------------
99 | | Cache Key Prefix
100 | |--------------------------------------------------------------------------
101 | |
102 | | When utilizing the APC, database, memcached, Redis, or DynamoDB cache
103 | | stores there might be other applications using the same cache. For
104 | | that reason, you may prefix every cache key to avoid collisions.
105 | |
106 | */
107 |
108 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
109 |
110 | ];
111 |
--------------------------------------------------------------------------------
/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
21 |
22 | /*
23 | |--------------------------------------------------------------------------
24 | | Deprecations Log Channel
25 | |--------------------------------------------------------------------------
26 | |
27 | | This option controls the log channel that should be used to log warnings
28 | | regarding deprecated PHP and library features. This allows you to get
29 | | your application ready for upcoming major versions of dependencies.
30 | |
31 | */
32 |
33 | 'deprecations' => [
34 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
35 | 'trace' => false,
36 | ],
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Log Channels
41 | |--------------------------------------------------------------------------
42 | |
43 | | Here you may configure the log channels for your application. Out of
44 | | the box, Laravel uses the Monolog PHP logging library. This gives
45 | | you a variety of powerful log handlers / formatters to utilize.
46 | |
47 | | Available Drivers: "single", "daily", "slack", "syslog",
48 | | "errorlog", "monolog",
49 | | "custom", "stack"
50 | |
51 | */
52 |
53 | 'channels' => [
54 | 'stack' => [
55 | 'driver' => 'stack',
56 | 'channels' => ['single'],
57 | 'ignore_exceptions' => false,
58 | ],
59 |
60 | 'single' => [
61 | 'driver' => 'single',
62 | 'path' => storage_path('logs/laravel.log'),
63 | 'level' => env('LOG_LEVEL', 'debug'),
64 | ],
65 |
66 | 'daily' => [
67 | 'driver' => 'daily',
68 | 'path' => storage_path('logs/laravel.log'),
69 | 'level' => env('LOG_LEVEL', 'debug'),
70 | 'days' => 14,
71 | ],
72 |
73 | 'slack' => [
74 | 'driver' => 'slack',
75 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
76 | 'username' => 'Laravel Log',
77 | 'emoji' => ':boom:',
78 | 'level' => env('LOG_LEVEL', 'critical'),
79 | ],
80 |
81 | 'papertrail' => [
82 | 'driver' => 'monolog',
83 | 'level' => env('LOG_LEVEL', 'debug'),
84 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
85 | 'handler_with' => [
86 | 'host' => env('PAPERTRAIL_URL'),
87 | 'port' => env('PAPERTRAIL_PORT'),
88 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
89 | ],
90 | ],
91 |
92 | 'stderr' => [
93 | 'driver' => 'monolog',
94 | 'level' => env('LOG_LEVEL', 'debug'),
95 | 'handler' => StreamHandler::class,
96 | 'formatter' => env('LOG_STDERR_FORMATTER'),
97 | 'with' => [
98 | 'stream' => 'php://stderr',
99 | ],
100 | ],
101 |
102 | 'syslog' => [
103 | 'driver' => 'syslog',
104 | 'level' => env('LOG_LEVEL', 'debug'),
105 | ],
106 |
107 | 'errorlog' => [
108 | 'driver' => 'errorlog',
109 | 'level' => env('LOG_LEVEL', 'debug'),
110 | ],
111 |
112 | 'null' => [
113 | 'driver' => 'monolog',
114 | 'handler' => NullHandler::class,
115 | ],
116 |
117 | 'emergency' => [
118 | 'path' => storage_path('logs/laravel.log'),
119 | ],
120 | ],
121 |
122 | ];
123 |
--------------------------------------------------------------------------------
/app/Base/System/Telegram/TelegramClient.php:
--------------------------------------------------------------------------------
1 | post($url, [
51 | 'json' => $parameters,
52 | 'connect_timeout' => 5,
53 | 'timeout' => 60,
54 | 'headers' => [
55 | 'Content-Type' => 'application/json'
56 | ]
57 | ]);
58 |
59 | // Decodes the JSON response body into an associative array.
60 | $result = json_decode($response->getBody()->getContents(), true);
61 |
62 | return $result; // Returns the response content.
63 | } catch (\Throwable $th) {
64 | // Catches any exceptions that occur during the request and returns the error message.
65 | return $th->getMessage();
66 | }
67 | }
68 |
69 | /**
70 | * Generates the final URL by appending the token to the provided endpoint.
71 | *
72 | * @param string $endPoint The base endpoint URL.
73 | * @return string The final URL with the token appended.
74 | */
75 | public function getFinalUrl($endPoint): string
76 | {
77 | // Concatenates the token with the provided endpoint to form the final URL.
78 | return $endPoint . $this->token . "/";
79 | }
80 |
81 | /**
82 | * Get the value of parameters
83 | */
84 | public function getParameters()
85 | {
86 | // Returns the value of the parameters property.
87 | return $this->parameters;
88 | }
89 |
90 | /**
91 | * Set the value of parameters
92 | *
93 | * @param array $parameters The parameters to set.
94 | * @return self
95 | */
96 | public function setParameters($parameters)
97 | {
98 | // Sets the value of the parameters property to the provided parameters array.
99 | $this->parameters = $parameters;
100 |
101 | // Returns the instance of the class for method chaining.
102 | return $this;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_MAILER', 'smtp'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Mailer Configurations
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure all of the mailers used by your application plus
24 | | their respective settings. Several examples have been configured for
25 | | you and you are free to add your own as your application requires.
26 | |
27 | | Laravel supports a variety of mail "transport" drivers to be used while
28 | | sending an e-mail. You will specify which one you are using for your
29 | | mailers below. You are free to add additional mailers as required.
30 | |
31 | | Supported: "smtp", "sendmail", "mailgun", "ses",
32 | | "postmark", "log", "array", "failover"
33 | |
34 | */
35 |
36 | 'mailers' => [
37 | 'smtp' => [
38 | 'transport' => 'smtp',
39 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
40 | 'port' => env('MAIL_PORT', 587),
41 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
42 | 'username' => env('MAIL_USERNAME'),
43 | 'password' => env('MAIL_PASSWORD'),
44 | 'timeout' => null,
45 | 'local_domain' => env('MAIL_EHLO_DOMAIN'),
46 | ],
47 |
48 | 'ses' => [
49 | 'transport' => 'ses',
50 | ],
51 |
52 | 'mailgun' => [
53 | 'transport' => 'mailgun',
54 | // 'client' => [
55 | // 'timeout' => 5,
56 | // ],
57 | ],
58 |
59 | 'postmark' => [
60 | 'transport' => 'postmark',
61 | // 'client' => [
62 | // 'timeout' => 5,
63 | // ],
64 | ],
65 |
66 | 'sendmail' => [
67 | 'transport' => 'sendmail',
68 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
69 | ],
70 |
71 | 'log' => [
72 | 'transport' => 'log',
73 | 'channel' => env('MAIL_LOG_CHANNEL'),
74 | ],
75 |
76 | 'array' => [
77 | 'transport' => 'array',
78 | ],
79 |
80 | 'failover' => [
81 | 'transport' => 'failover',
82 | 'mailers' => [
83 | 'smtp',
84 | 'log',
85 | ],
86 | ],
87 | ],
88 |
89 | /*
90 | |--------------------------------------------------------------------------
91 | | Global "From" Address
92 | |--------------------------------------------------------------------------
93 | |
94 | | You may wish for all e-mails sent by your application to be sent from
95 | | the same address. Here, you may specify a name and address that is
96 | | used globally for all e-mails that are sent by your application.
97 | |
98 | */
99 |
100 | 'from' => [
101 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
102 | 'name' => env('MAIL_FROM_NAME', 'Example'),
103 | ],
104 |
105 | /*
106 | |--------------------------------------------------------------------------
107 | | Markdown Mail Settings
108 | |--------------------------------------------------------------------------
109 | |
110 | | If you are using Markdown based email rendering, you may configure your
111 | | theme and component paths here, allowing you to customize the design
112 | | of the emails. Or, you may simply stick with the Laravel defaults!
113 | |
114 | */
115 |
116 | 'markdown' => [
117 | 'theme' => 'default',
118 |
119 | 'paths' => [
120 | resource_path('views/vendor/mail'),
121 | ],
122 | ],
123 |
124 | ];
125 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => 'web',
18 | 'passwords' => 'users',
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | here which uses session storage and the Eloquent user provider.
29 | |
30 | | All authentication drivers have a user provider. This defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | mechanisms used by this application to persist your user's data.
33 | |
34 | | Supported: "session"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 | ],
44 |
45 | /*
46 | |--------------------------------------------------------------------------
47 | | User Providers
48 | |--------------------------------------------------------------------------
49 | |
50 | | All authentication drivers have a user provider. This defines how the
51 | | users are actually retrieved out of your database or other storage
52 | | mechanisms used by this application to persist your user's data.
53 | |
54 | | If you have multiple user tables or models you may configure multiple
55 | | sources which represent each model / table. These sources may then
56 | | be assigned to any extra authentication guards you have defined.
57 | |
58 | | Supported: "database", "eloquent"
59 | |
60 | */
61 |
62 | 'providers' => [
63 | 'users' => [
64 | 'driver' => 'eloquent',
65 | 'model' => App\Models\User::class,
66 | ],
67 |
68 | // 'users' => [
69 | // 'driver' => 'database',
70 | // 'table' => 'users',
71 | // ],
72 | ],
73 |
74 | /*
75 | |--------------------------------------------------------------------------
76 | | Resetting Passwords
77 | |--------------------------------------------------------------------------
78 | |
79 | | You may specify multiple password reset configurations if you have more
80 | | than one user table or model in the application and you want to have
81 | | separate password reset settings based on the specific user types.
82 | |
83 | | The expire time is the number of minutes that each reset token will be
84 | | considered valid. This security feature keeps tokens short-lived so
85 | | they have less time to be guessed. You may change this as needed.
86 | |
87 | | The throttle setting is the number of seconds a user must wait before
88 | | generating more password reset tokens. This prevents the user from
89 | | quickly generating a very large amount of password reset tokens.
90 | |
91 | */
92 |
93 | 'passwords' => [
94 | 'users' => [
95 | 'provider' => 'users',
96 | 'table' => 'password_reset_tokens',
97 | 'expire' => 60,
98 | 'throttle' => 60,
99 | ],
100 | ],
101 |
102 | /*
103 | |--------------------------------------------------------------------------
104 | | Password Confirmation Timeout
105 | |--------------------------------------------------------------------------
106 | |
107 | | Here you may define the amount of seconds before a password confirmation
108 | | times out and the user is prompted to re-enter their password via the
109 | | confirmation screen. By default, the timeout lasts for three hours.
110 | |
111 | */
112 |
113 | 'password_timeout' => 10800,
114 |
115 | ];
116 |
--------------------------------------------------------------------------------
/app/Base/System/Traits/App/UploadTrait.php:
--------------------------------------------------------------------------------
1 | fileCategory($ext);
19 | // Initializes a new TelegramClient instance with the provided token and chat ID.
20 | $telegramClient = new TelegramClient($this->token, $this->chatID);
21 |
22 | // Sends the photo to Telegram using the TelegramClient's sendPhoto method and stores the result.
23 | if ($category === "image")
24 | $file = $telegramClient->sendPhoto($url);
25 | elseif ($category === "document")
26 | $file = $telegramClient->sendDocument($url);
27 | elseif ($category === "audio")
28 | $file = $telegramClient->sendAudio($url);
29 | elseif ($category === "video")
30 | $file = $telegramClient->sendVideo($url);
31 | // Retrieves information about the uploaded file using the TelegramClient's getFile method.
32 | $result = $telegramClient->getFile($this->getFileID($file));
33 |
34 | // Constructs and returns the final URL of the uploaded file.
35 | return $telegramClient->getFinalUrl($this->getFileEndPoint) . $result['result']['file_path'];
36 | }
37 |
38 | /**
39 | * Extracts the file ID from the response returned by Telegram after uploading a file.
40 | *
41 | * @param array $file The response returned by Telegram after uploading a file.
42 | * @return string|null The ID of the uploaded file, or null if the file type is not supported.
43 | */
44 | private function getFileID($file)
45 | {
46 | // Checks if the response contains information about a photo file.
47 | if (isset($file['result']['photo'][0]['file_id'])) {
48 | // Returns the file ID of the last photo in the array (assuming multiple photos were uploaded).
49 | return end($file['result']['photo'])['file_id'];
50 | }
51 | // Checks if the response contains information about a document file.
52 | if (isset($file['result']['document'])) {
53 | // Returns the file ID of the document.
54 | return $file['result']['document']['file_id'];
55 | }
56 | // Checks if the response contains information about an audio file.
57 | if (isset($file['result']['audio'])) {
58 | // Returns the file ID of the audio file.
59 | return $file['result']['audio']['file_id'];
60 | }
61 | // Checks if the response contains information about a video file.
62 | if (isset($file['result']['video'])) {
63 | // Returns the file ID of the video file.
64 | return $file['result']['video']['file_id'];
65 | }
66 | }
67 |
68 | /**
69 | * Determines the category of a file based on its extension.
70 | *
71 | * @param string $extension The file extension.
72 | * @return string The category of the file (image, document, audio, video, or unknown).
73 | */
74 | private function fileCategory($extension)
75 | {
76 | // Define an associative array with categories and their associated file extensions.
77 | $categories = [
78 | 'image' => ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff'],
79 | 'document' => ['pdf', 'doc', 'docx', 'txt', 'odt', 'rtf', 'xls', 'xlsx', 'ods', 'csv'],
80 | 'audio' => ['mp3', 'wav', 'aac', 'flac'],
81 | 'video' => ['mp4', 'avi', 'mkv', 'mov', 'wmv']
82 | ];
83 |
84 | // Normalize the extension to lowercase.
85 | $extension = strtolower($extension);
86 |
87 | // Loop through the categories and return the matching category.
88 | foreach ($categories as $category => $extensions) {
89 | if (in_array($extension, $extensions)) {
90 | // Returns the category if the extension matches one of the extensions in the array.
91 | return $category;
92 | }
93 | }
94 |
95 | // Returns 'unknown' if the extension is not found in any category.
96 | return 'unknown';
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/public/ss/script/app.js:
--------------------------------------------------------------------------------
1 | const upload = document.getElementById("upload");
2 | const uploadForm = document.getElementById("upload-form");
3 | const uploadZone = document.getElementById("upload-zone");
4 | const drag = document.querySelector(".drag");
5 | const drop = document.querySelector(".drop");
6 |
7 | /**
8 | * Copies the given text message to the clipboard.
9 | *
10 | * @param {string} text The message to be copied to the clipboard.
11 | */
12 | function copyMessage(text) {
13 | // Create a temporary textarea element to hold the text.
14 | const textarea = document.createElement('textarea');
15 |
16 | // Set the value of the textarea to the text to be copied.
17 | textarea.value = text;
18 |
19 | // Make sure the textarea is not visible on the screen.
20 | textarea.style.position = 'fixed'; // Prevent scrolling to the bottom of the page in MS Edge.
21 | textarea.style.opacity = '0';
22 |
23 | // Append the textarea to the document body.
24 | document.body.appendChild(textarea);
25 |
26 | // Select the text in the textarea.
27 | textarea.select();
28 | textarea.setSelectionRange(0, 99999); // For mobile devices
29 |
30 | try {
31 | // Copy the selected text to the clipboard.
32 | const successful = document.execCommand('copy');
33 | if (successful) {
34 | console.log('Text copied to clipboard successfully!');
35 | } else {
36 | console.error('Failed to copy text to clipboard.');
37 | }
38 | } catch (err) {
39 | // Log any errors that occur during the copy process.
40 | console.error('Unable to copy to clipboard', err);
41 | }
42 |
43 | }
44 |
45 | uploadZone.addEventListener("dragover", (e) => {
46 | drop.classList.remove("hidden");
47 | drop.classList.add("block");
48 | drag.classList.add("hidden");
49 | drag.classList.remove("block");
50 | e.preventDefault();
51 | });
52 |
53 | uploadZone.addEventListener("drop", (e) => {
54 | document.getElementById("upload").files = e.dataTransfer.files;
55 | drop.classList.remove("block");
56 | drop.classList.add("hidden");
57 | drag.classList.remove("hidden");
58 | drag.classList.add("block");
59 | uploadFileHandler();
60 | e.preventDefault();
61 | });
62 |
63 | function uploadFileHandler() {
64 | const files = upload.files;
65 |
66 | for (let i = 0; i < files.length; i++) {
67 | const xhr = new XMLHttpRequest();
68 | xhr.open("POST", "");
69 |
70 | const formData = (files[i].name, files[i]);
71 | const progressBlock = addProgressBar(files[i]);
72 |
73 | xhr.upload.addEventListener("progress", (event) => {
74 | const progressBar = progressBlock.querySelector(".progress-bar div");
75 | const progressText = progressBlock.querySelector(".progress-bar p");
76 | const fileHeader = progressBlock.querySelector(".file-header");
77 |
78 | if (event.lengthComputable == true) {
79 | const percent = ((event.loaded / event.total) * 100).toFixed(1);
80 | progressBar.style.width = percent + "%";
81 | progressBar.classList.add("progressing");
82 | progressText.textContent = percent + "%";
83 | progressText.classList.add("fade");
84 |
85 | const progressBarWidth = parseInt(progressBar.style.width);
86 |
87 | if (progressBarWidth > 50) {
88 | progressText.classList.add("text-slate-50");
89 | }
90 |
91 | if (progressBarWidth == 100) {
92 | fileHeader.classList.add("show");
93 | progressText.textContent = "تکمیل شد";
94 | progressText.classList.remove("fade");
95 | setTimeout(() => {
96 | progressText.innerHTML = `