Как создать свой собственный Taplink с помощью Python и GitHub Pages
- пятница, 30 августа 2024 г. в 00:00:06
Я случайно наткнулся на статью автора Lucas Neves Pereira под названием "Build your own LinkTree with Go and GitHub Pages". В статье описано, как создать подобие LinkTree (аналог Taplink) на языке Go и GitHub Pages. Я, как любитель языка Python, решил реализовать проект на этом языке.
Первым делом создадим файловую структуру для нашего проекта. Мы организуем наш проект таким образом, чтобы он был легко поддерживаемым и удобным для развертывания на GitHub Pages.
Файловая структура:
/ (root)
|-- /docs
| |-- index.html
| |-- /assets
| |-- (файлы стилей, скриптов, иконок и т.д.)
|-- config.yml
|-- generate_site.py
|-- /themes
/docs: В этой папке будут храниться сгенерированные HTML-файлы и все необходимые ассеты (изображения, стили, скрипты). Эта папка будет использоваться для развертывания сайта на GitHub Pages.
config.yml: Файл конфигурации, который содержит все данные для персонализации сайта.
generate_site.py: Скрипт на Python, который будет генерировать сайт на основе данных из config.yml.
/themes: Папка с темами для сайта. В нашем случае, здесь хранится единственная тема custom, которая включает в себя шаблон HTML, стили, скрипты и изображения.
Файл config.yml содержит данные о пользователе и ссылки, которые будут отображаться на сайте. Вот его содержимое:
name: "King Triton"
picture: "assets/img/picture.jpg"
bio: "Programmer python and php/laravel"
meta:
lang: "en"
description: "Programmer python and php/laravel"
title: "King Triton"
author: "King Triton"
siteUrl: "https://king-tri-ton.github.io/pythonpagelink/"
links:
- name: "Github"
url: "https://github.com/king-tri-ton"
- name: "Dev.to"
url: "https://dev.to/king_triton"
- name: "Patreon"
url: "https://www.patreon.com/king_triton"
- name: "Telegram"
url: "https://t.me/king_triton"
- name: "Instagram"
url: "https://www.instagram.com/king_tri_ton"
theme: "custom"
name: Имя пользователя, которое будет отображаться на сайте.
picture: Путь к изображению пользователя.
bio: Краткая биография пользователя.
meta: Метаинформация сайта (язык, описание, заголовок, автор, URL сайта).
links: Список ссылок, которые будут отображаться на сайте. Каждый элемент содержит название и URL.
theme: Тема сайта, которую следует использовать.
Далее мы напишем скрипт на Python, который будет использовать шаблон из темы, данные из config.yml и генерировать готовый HTML-файл.
import os
import shutil
from jinja2 import Environment, FileSystemLoader
import yaml
# Загрузка конфигурации
with open('config.yml', 'r') as config_file:
config = yaml.safe_load(config_file)
# Создание выходной директории
output_dir = 'docs'
os.makedirs(output_dir, exist_ok=True)
# Настройка Jinja2
env = Environment(loader=FileSystemLoader('themes/custom'))
template = env.get_template('index.html')
# Генерация HTML файла
output_html = template.render(config=config)
with open(os.path.join(output_dir, 'index.html'), 'w') as fh:
fh.write(output_html)
# Копирование папки assets в выходной каталог
assets_source = os.path.join('themes', config['theme'], 'assets')
assets_dest = os.path.join(output_dir, 'assets')
if os.path.exists(assets_source):
shutil.copytree(assets_source, assets_dest, dirs_exist_ok=True)
print("Site generated successfully.")
Загрузка конфигурации: Скрипт загружает данные из файла config.yml.
Создание выходной директории: Папка docs создается автоматически, если она не существует.
Настройка Jinja2: Используется Jinja2 для загрузки шаблона HTML и рендеринга контента.
Генерация HTML-файла: Скрипт генерирует файл index.html с использованием данных из конфигурации и сохраняет его в папке docs.
Копирование ассетов: Все ассеты (CSS, изображения, скрипты) копируются в папку docs/assets.
Теперь создадим тему, которая будет использоваться для нашего сайта. В папке themes/custom/ должны находиться следующие файлы:
Это основной HTML-шаблон сайта. Он использует переменные из файла конфигурации.
<!DOCTYPE html>
<html lang="{{ config.meta.lang }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{ config.meta.description }}">
<title>{{ config.meta.title }}</title>
<meta name="author" content="{{ config.meta.author }}">
<link rel="canonical" href="{{ config.meta.siteUrl }}">
<link rel="icon" type="image/x-icon" href="assets/icons/favicon.ico">
<link rel="stylesheet" href="assets/css/styles.css">
<meta property="og:title" content="{{ config.meta.title }}">
<meta property="og:site_name" content="{{ config.meta.title }}">
<meta property="og:description" content="{{ config.meta.description }}">
<meta property="og:locale" content="{{ config.meta.lang }}">
<meta name="twitter:title" content="{{ config.meta.title }}">
<meta name="twitter:description" content="{{ config.meta.description }}">
</head>
<body>
<header>
<img src="{{ config.picture }}" alt="Picture" class="avatar">
<h1>{{ config.name }}</h1>
<small class="bio">{{ config.bio }}</small>
</header>
<main>
<section class="links">
{% for link in config.links %}
<a class="link-item" href="{{ link.url }}" target="_blank" rel="noopener noreferrer">
<p>{{ link.name }}</p>
</a>
{% endfor %}
</section>
</main>
<footer>
<small>© <span class="year"></span> {{ config.meta.author }}</small>
</footer>
<script src="assets/js/script.js"></script>
</body>
</html>
Файл CSS для стилизации страницы.
/* CSS Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Variables */
:root {
--max-width: 600px;
--font-family: 'Inter', sans-serif;
--padding: 1rem;
--header-margin-bottom: 1rem;
--line-height: 2;
--font-size: 16px;
--primary-color-light: #ffffff;
--background-color-light: #f0f0f0;
--text-color-light: #333;
--link-color-light: #1a73e8;
--bio-color-light: #666;
--primary-color-dark: #1e1e1e;
--background-color-dark: #121212;
--text-color-dark: #e0e0e0;
--link-color-dark: #8ab4f8;
--bio-color-dark: #aaa;
}
/* Light Theme */
@media (prefers-color-scheme: light) {
:root {
--primary-color: var(--primary-color-light);
--background-color: var(--background-color-light);
--text-color: var(--text-color-light);
--link-color: var(--link-color-light);
--bio-color: var(--bio-color-light);
}
}
/* Dark Theme */
@media (prefers-color-scheme: dark) {
:root {
--primary-color: var(--primary-color-dark);
--background-color: var(--background-color-dark);
--text-color: var(--text-color-dark);
--link-color: var(--link-color-dark);
--bio-color: var(--bio-color-dark);
}
}
/* Global Styles */
html {
font-family: var(--font-family);
font-size: var(--font-size);
line-height: var(--line-height);
}
body {
max-width: var(--max-width);
min-height: 100vh;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
background-color: var(--background-color);
color: var(--text-color);
padding: var(--padding);
}
/* Header Styles */
header {
padding: var(--padding) 0;
margin-bottom: var(--header-margin-bottom);
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.avatar {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--primary-color);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
margin-bottom: 0.5rem;
}
.bio {
font-size: 14px;
color: var(--bio-color);
margin-bottom: 1rem;
}
/* Main Content Styles */
main {
width: 100%;
flex: 1;
}
.links {
display: flex;
flex-direction: column;
gap: 1rem;
text-align: center;
overflow-y: auto;
max-height: 400px;
}
.link-item {
display: block;
padding: 16px 20px;
text-decoration: none;
color: var(--link-color);
background: var(--primary-color);
border-radius: 12px;
border: 1px solid var(--link-color);
transition: background-color 0.25s, color 0.25s;
}
.link-item:hover,
.link-item:focus {
background-color: var(--link-color);
color: var(--primary-color);
}
.link-item p {
line-height: 1.5;
font-weight: 500;
}
/* Footer Styles */
footer {
width: 100%;
text-align: center;
padding: 1rem 0;
font-size: 14px;
gap: 1rem;
display: flex;
justify-content: center;
align-items: center;
}
/* ScrollBar */
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: transparent;
}
::-webkit-scrollbar-thumb:hover {
background: transparent;
}
Файл JavaScript для базовых функциональностей.
console.log("scripts loaded");
const yearDate = new Date().getFullYear().toString();
document.querySelector(".year").innerText = yearDate;
Фотография, которая будет использоваться в качестве аватара.
После того как все файлы созданы, запустите скрипт generate_site.py, чтобы сгенерировать сайт:
python generate_site.py
Сайт будет сгенерирован в папке docs.
Создайте новый репозиторий на GitHub.
Загрузите все файлы, включая папку docs, в репозиторий.
Перейдите в раздел Settings репозитория.
В разделе Pages выберите ветку master и папку /docs как источник.
Сохраните изменения и подождите, пока GitHub Pages развернет ваш сайт.
Теперь ваш сайт будет доступен по адресу https://[username].github.io/[repository-name]/
Вот и все! Теперь у вас есть собственный сайт в стиле Taplink, созданный на Python и развернутый на GitHub Pages. Вы можешь посмотреть мой готовый результат по адресу https://king-tri-ton.github.io/pythonpagelink/.
Благодарю за внимание!