diff --git a/.github/workflows/publish-ghcr-unstable.yaml b/.github/workflows/publish-ghcr-unstable.yaml index 5f28464..701db9d 100644 --- a/.github/workflows/publish-ghcr-unstable.yaml +++ b/.github/workflows/publish-ghcr-unstable.yaml @@ -18,7 +18,7 @@ jobs: - name: Build and push run: | export TZ="Asia/Taipei" - VERSION=$(date +'%Y-%m-%d.%H.%M.%S') + VERSION=unstable-$(date +'%Y-%m-%d.%H.%M.%S') docker login ghcr.io -u kevinlee-06 -p ${{secrets.GH_PAT}}; docker build -t ghcr.io/kevinlee-06/linklie:${VERSION} . docker tag ghcr.io/kevinlee-06/linklie:${VERSION} ghcr.io/kevinlee-06/linklie:unstable diff --git a/docs/api.html b/docs/api.html new file mode 100644 index 0000000..f6b82de --- /dev/null +++ b/docs/api.html @@ -0,0 +1,93 @@ + + + + + + + API - Linklie 短網址伺服器 + + + +
+

Linklie 短網址伺服器

+ +
+
+

概述

+

此 API 允許用戶使用自定義 ID 和密碼來縮短 URL,以便將來刪除。API 端點接受 POST 請求以在數據庫中創建縮短的 URL 條目。

+ +

端點

+ +

POST `/shorten`

+

此端點用於縮短給定的 URL。

+ +

請求主體

+

請求主體必須是包含以下字段的 JSON 對象:

+ + +

範例請求

+
{
+    "url": "https://long-url.com",
+    "id": "short-id",
+    "password": "password-for-deletion"
+}
+ +

回應

+

回應將是一個 JSON,包含原始 URL、自定義 ID 和用於刪除的密碼。

+ +
成功 (HTTP 200)
+
{
+    "url": "https://long-url.com",
+    "id": "short-id",
+    "password": "password-for-deletion"
+}
+ +
+

注意:

+ +
+ +
錯誤回應:
+
    + HTTP 400 +
+ +

範例用法

+

要縮短 URL,您可以使用像 curl 這樣的工具:

+
curl -X POST https://LINKLIE_SERVER/shorten \
+        -H "Content-Type: application/json" \
+        -d '{
+            "url": "https://long-url.com",
+            "id": "short-id",
+            "password": "password-for-deletion"
+        }'
+
+

如果成功,這將回傳縮短的 URL 詳情,或者如果請求有問題,則回傳錯誤消息。

+ +
+

注意:

+

不要忘記將 LINKLIE_SERVER 替換為您的 Linklie 伺服器的實際 URL。

+
+
+ + + diff --git a/docs/contributing.html b/docs/contributing.html new file mode 100644 index 0000000..2fbdd63 --- /dev/null +++ b/docs/contributing.html @@ -0,0 +1,39 @@ + + + + + + + Contributing - Linklie 短網址伺服器 + + + +
+

Linklie 短網址伺服器

+ +
+
+

How to Contribute

+

We welcome contributions! Here’s how you can help:

+ +
+ + + + diff --git a/docs/deploying.html b/docs/deploying.html new file mode 100644 index 0000000..7f6ebf0 --- /dev/null +++ b/docs/deploying.html @@ -0,0 +1,32 @@ + + + + + + + Deploying - Linklie 短網址伺服器 + + + +
+

Linklie 短網址伺服器

+ +
+
+

如何部屬

+

有關如何部屬應用程式的指示,請參閱 部屬指南

+
+ + + diff --git a/docs/features.html b/docs/features.html new file mode 100644 index 0000000..6cfb216 --- /dev/null +++ b/docs/features.html @@ -0,0 +1,83 @@ + + + + + + + Features - Linklie 短網址伺服器 + + + +
+

Linklie 短網址伺服器

+ +
+
+
+
+ + + An NPC Project + +
+
+ +

功能

+ +

刪除的密碼保護

+ + +

網頁介面

+ + Web UI + +

自定義 URL

+ + +

開源

+ + +

輕鬆部署

+ + +

API

+ + +

無日誌政策

+ +
+ + + diff --git a/docs/image.png b/docs/image.png index 664f00f..a5adecc 100644 Binary files a/docs/image.png and b/docs/image.png differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..6afe118 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,41 @@ + + + + + + + Linklie 短網址伺服器 + + + +
+

Linklie 短網址伺服器

+ +
+
+
+
+

UI 展示

+

+ ntut.uk + 上面可以試用,但資料會不定期清理~ +

+
+ +
+ +
+ + + diff --git a/docs/logo.jpg b/docs/logo.jpg deleted file mode 100644 index 96556da..0000000 Binary files a/docs/logo.jpg and /dev/null differ diff --git a/docs/styles.css b/docs/styles.css new file mode 100644 index 0000000..7b9d17f --- /dev/null +++ b/docs/styles.css @@ -0,0 +1,113 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f4f4f4; + padding-bottom: 70px; /* Space for the footer */ +} + +header { + background: #35424a; + color: #ffffff; + padding: 10px 0; + text-align: center; +} + +nav ul { + list-style: none; + padding: 0; +} + +nav ul li { + display: inline; + margin: 0 15px; +} + +nav ul li a { + color: #ffffff; + text-decoration: none; + transition: color 0.3s; /* Smooth transition for hover effect */ +} + +nav ul li a:hover { + color: #f4f4f4; /* Change color on hover */ +} + +main { + padding: 20px; + background: #ffffff; + margin: 20px; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +h2 { + color: #35424a; /* Change heading color */ +} + +h4, h5 { + color: #333; /* Darker color for subheadings */ +} + +p { + font-size: 16px; /* 設定段落字體大小 */ + line-height: 1.5; /* 行高 */ + color: #333; /* 段落文字顏色 */ +} + +h5 { + font-size: 18px; /* 設定 h5 字體大小 */ + line-height: 1.4; /* 行高 */ + color: #35424a; /* h5 文字顏色 */ + margin-top: 10px; /* 上方邊距 */ + margin-bottom: 10px; /* 下方邊距 */ +} + + +pre { + background: #f8f8f8; /* Light background for code blocks */ + border: 1px solid #ddd; /* Border around code blocks */ + padding: 10px; /* Padding inside code blocks */ + border-radius: 5px; /* Rounded corners */ + overflow-x: auto; /* Allow horizontal scrolling */ +} + +footer { + text-align: center; + padding: 10px 0; + background: #35424a; + color: #ffffff; + position: fixed; /* Keep footer fixed at the bottom */ + bottom: 0; + width: 100%; +} + +blockquote { + border-left: 4px solid #35424a; /* Left border for blockquotes */ + padding-left: 10px; /* Padding for blockquote text */ + margin: 10px 0; /* Margin for blockquote */ + color: #555; /* Color for blockquote text */ +} + +.flex-container { + display: flex; + justify-content: space-between; /* Space between items */ + align-items: center; /* Center items vertically */ +} + +.logo { + height: 140px; + display: block; /* Ensure images are block elements */ +} + +/* Responsive Design */ +@media (max-width: 600px) { + nav ul li { + display: block; /* Stack navigation items on small screens */ + margin: 5px 0; /* Margin for stacked items */ + } + + main { + margin: 10px; /* Reduce margin on small screens */ + } +} diff --git a/public/index.html b/public/index.html index 3915122..d88ecb8 100644 --- a/public/index.html +++ b/public/index.html @@ -3,64 +3,99 @@ - Linklie - URL Shortener + Linklie - URL Shortener -

Linklie - A URL Shortener

+ +

+ + Linklie - A URL Shortener + +

-

Create Short URL

+

Create Short URL

- -

+ +

- -

+ +

- -

+ +

- +
-

Delete Short URL

+

Delete Short URL

- -

+ +

- +

- +
-

Response: - +

+ Response +

-

+        
+ +

+
+
+ +

+ + + + +
+
-
- - +
+ + +
+
+ +
- +
+ + + diff --git a/public/new.html b/public/new.html new file mode 100644 index 0000000..6980eb1 --- /dev/null +++ b/public/new.html @@ -0,0 +1,59 @@ + + + + + + Linklie - URL Shortener + + + + +

+ Linklie - A URL Shortener +

+ +
+

Create Short URL

+
+ +
+ + +
+ + +
+ + +
+
+ +
+

Delete Short URL

+
+ +
+ + +
+ + +
+
+ +
+

Response: + +

+

+    
+ + + + + \ No newline at end of file diff --git a/public/scripts/copy.js b/public/scripts/copy.js new file mode 100644 index 0000000..dd819c1 --- /dev/null +++ b/public/scripts/copy.js @@ -0,0 +1,14 @@ +function copyText() { + var copyText = document.getElementById("shortenedUrl"); + copyText.select(); + copyText.setSelectionRange(0, 99999); + document.execCommand("copy"); + // alert("已複製訊息: " + copyText.value); + const message = document.getElementById("copyMessage"); + message.style.display = "block"; + + // Hide the message after 1.5 seconds + setTimeout(() => { + message.style.display = "none"; + }, 1500); +} \ No newline at end of file diff --git a/public/scripts/delete.js b/public/scripts/delete.js index 0876cd4..a93aa5a 100644 --- a/public/scripts/delete.js +++ b/public/scripts/delete.js @@ -8,6 +8,8 @@ document.getElementById('deleteForm').addEventListener('submit', function(event) password: deletePassword }; + document.getElementById('shortenedUrl').value = ""; + fetch(`/${deleteId}`, { method: 'DELETE', headers: { @@ -17,14 +19,16 @@ document.getElementById('deleteForm').addEventListener('submit', function(event) }) .then(response => { if (!response.ok) { - throw new Error('Network response was not ok: ' + response.statusText); + throw new Error(response.statusText); } return response.text(); }) .then(message => { - document.getElementById('responseMessage').textContent = message; + document.getElementById('responseMessage').value = message; + document.getElementById('response-h2').scrollIntoView({ behavior: 'smooth' }); }) .catch(error => { - document.getElementById('responseMessage').textContent = 'Error: ' + error.message; + document.getElementById('responseMessage').value = 'Error: ' + error.message; + document.getElementById('response-h2').scrollIntoView({ behavior: 'smooth' }); }); }); \ No newline at end of file diff --git a/public/scripts/i18n.js b/public/scripts/i18n.js new file mode 100644 index 0000000..d2872ed --- /dev/null +++ b/public/scripts/i18n.js @@ -0,0 +1,79 @@ +const translations = { + en: { + "title": "Linklie - URL Shortener", + "main-title": "Linklie - A URL Shortener", + "create-title": "Create Short URL", + "url-label": "URL:", + "short-id-label": "Short ID:", + "password-label": "Password:", + "create-button": "Create Short URL", + "delete-title": "Delete Short URL", + "delete-id-label": "Short ID:", + "delete-password-label": "Password:", + "delete-button": "Delete Short URL", + "response-title": "Response:", + "response-tips": "[tips]", + "theme-label": "Choose a theme:", + "theme-orange": "Orange", + "theme-green": "Green", + "theme-blue": "Blue", + "theme-pink": "Pink", + "theme-dark": "Dark", + "theme-black": "Black", + "theme-tailwind": "Tailwind", + "language-label": "語言:", + "shortened-url": "Shortened URL:", + "responseMessage": "Response Message", + }, + "zh-TW": { + "title": "Linklie - 短網址", + "main-title": "Linklie - 一個短網址工具", + "create-title": "建立短網址", + "url-label": "長網址:", + "short-id-label": "自訂識別碼:", + "password-label": "密碼:", + "create-button": "建立短網址", + "delete-title": "刪除短網址", + "delete-id-label": "識別碼:", + "delete-password-label": "密碼:", + "delete-button": "刪除短網址", + "response-title": "回應", + "response-tips": "[提示]", + "theme-label": "選擇主題:", + "theme-orange": "橙色", + "theme-green": "綠色", + "theme-blue": "藍色", + "theme-pink": "粉色", + "theme-dark": "深藍色", + "theme-black": "黑色", + "theme-tailwind": "Tailwind", + "language-label": "Language:", + "shortened-url": "短網址:", + "responseMessage": "回應訊息", + } +}; + +document.addEventListener('DOMContentLoaded', () => { + const languageSelector = document.getElementById('language'); + const elementsToTranslate = document.querySelectorAll('[data-i18n]'); + + // Define the translatePage function in the global scope + window.translatePage = function(lang) { + elementsToTranslate.forEach(el => { + const key = el.getAttribute('data-i18n'); + if (translations[lang][key]) { + if (el.tagName === 'TITLE') { + document.title = translations[lang][key]; + } else { + el.textContent = translations[lang][key]; + } + } + }); + }; + + // Add event listener for language selection + languageSelector.addEventListener('change', (event) => { + const selectedLanguage = event.target.value; + translatePage(selectedLanguage); // Call the function here + }); +}); diff --git a/public/scripts/post.js b/public/scripts/post.js index e273708..d3ae27d 100644 --- a/public/scripts/post.js +++ b/public/scripts/post.js @@ -20,6 +20,7 @@ document.getElementById('postForm').addEventListener('submit', function(event) { }) .then(response => { if (!response.ok) { + document.getElementById('shortenedUrl').value = ""; return response.text().then(text => { throw new Error(text); }); @@ -27,9 +28,14 @@ document.getElementById('postForm').addEventListener('submit', function(event) { return response.json(); }) .then(data => { - document.getElementById('responseMessage').textContent = JSON.stringify(data, null, 2); + document.getElementById('responseMessage').value = JSON.stringify(data, null, 2); + const host = window.location.host; + const protocol = window.location.protocol; + document.getElementById('shortenedUrl').value = `${protocol}//${host}/${data.id}`; + document.getElementById('response-h2').scrollIntoView({ behavior: 'smooth' }); }) .catch(error => { - document.getElementById('responseMessage').textContent = 'Error: ' + error.message; + document.getElementById('responseMessage').value = 'Error: ' + error.message; + document.getElementById('response-h2').scrollIntoView({ behavior: 'smooth' }); }); }); \ No newline at end of file diff --git a/public/scripts/styles.js b/public/scripts/styles.js index 11d8f70..2c7f384 100644 --- a/public/scripts/styles.js +++ b/public/scripts/styles.js @@ -1,3 +1,7 @@ document.getElementById('theme').addEventListener('change', function() { - document.body.className = this.value; + if (this.value === 'tailwind') { + window.location.href = '/public/new.html'; + } else { + document.body.className = this.value; + } }); \ No newline at end of file diff --git a/public/status/404.html b/public/status/404.html new file mode 100644 index 0000000..0414faa --- /dev/null +++ b/public/status/404.html @@ -0,0 +1,98 @@ + + + + + + 404 Not Found + + + +
+

ERROR: 404 Not Found

+

The page you are looking for does not exist.

+ + + +
+ + diff --git a/public/styles.css b/public/styles.css index 8af20a0..7781d90 100644 --- a/public/styles.css +++ b/public/styles.css @@ -40,7 +40,7 @@ label { margin-bottom: 15px; } -input { +input, textarea { width: 100%; padding: 10px; box-sizing: border-box; @@ -48,6 +48,7 @@ input { border-radius: 5px; transition: border-color 0.3s; background-color: var(--container-background); + resize: none; } input:focus { @@ -86,4 +87,8 @@ a:hover { h2, #responseMessage, input { color: var(--text-color) +} + +.language-selector, .theme-selector { + margin-bottom: 20px; } \ No newline at end of file diff --git a/public/themes/pink.css b/public/themes/pink.css index c71e6c2..ee75297 100644 --- a/public/themes/pink.css +++ b/public/themes/pink.css @@ -1,13 +1,13 @@ .pink-theme { - --background-color: #fce4ec; /* Light pink background */ - --container-background: #ffffff; /* White container */ - --label-color: #d81b60; /* Pink label color */ - --input-border: #d81b60; /* Pink input border */ - --input-focus-border: #f48fb1; /* Light pink focus border */ - --button-background: #d81b60; /* Pink button background */ - --button-hover-background: #c2185b; /* Darker pink on hover */ - --link-color: #d81b60; /* Pink link color */ - --link-hover-color: #c2185b; /* Darker pink on hover */ - --button-text-color: #ffffff; /* White text on buttons */ - --text-color: #000000; /* Black text */ + --background-color: #fce4ec; + --container-background: #ffffff; + --label-color: #d81b60; + --input-border: #d81b60; + --input-focus-border: #f48fb1; + --button-background: #d81b60; + --button-hover-background: #c2185b; + --link-color: #d81b60; + --link-hover-color: #c2185b; + --button-text-color: #ffffff; + --text-color: #000000; } diff --git a/server.js b/server.js index 3f1c5e0..52dc5c6 100644 --- a/server.js +++ b/server.js @@ -87,7 +87,7 @@ app.get('/:id', (req, res) => { db.get("SELECT target_url FROM urls WHERE custom_id = ?", [id], (err, row) => { if (err || !row) { - return res.status(404).send('Not found'); + return res.status(404).sendFile(path.join(__dirname, "/public/status", "404.html")); } res.redirect(row.target_url); });