Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
editor and udpate footer
  • Loading branch information
yakhyo committed Feb 27, 2026
commit fcee48de7b95b576c5b07fe06786082416070667
4 changes: 2 additions & 2 deletions .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ module.exports = function (eleventyConfig) {
});

eleventyConfig.addCollection("tutorials", function (collectionApi) {
return collectionApi.getFilteredByGlob("tutorial/en/**/*.md").sort((a, b) => {
return collectionApi.getFilteredByGlob("content/en/tutorial/**/*.md").sort((a, b) => {
return (a.data.order || 0) - (b.data.order || 0);
});
});

eleventyConfig.addCollection("tutorialsUz", function (collectionApi) {
return collectionApi.getFilteredByGlob("tutorial/uz/**/*.md").sort((a, b) => {
return collectionApi.getFilteredByGlob("content/uz/tutorial/**/*.md").sort((a, b) => {
return (a.data.order || 0) - (b.data.order || 0);
});
});
Expand Down
2 changes: 2 additions & 0 deletions .eleventyignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ README.md
CONTRIBUTING.md
CODE_OF_CONDUCT.md
LICENSE
.github/**/*.md
docs/**/*.md
node_modules
_site
tailwind.css
Expand Down
7 changes: 7 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ Thank you for contributing.

- Keep tutorials clear, practical, and beginner-friendly.
- Use consistent front matter (`layout`, `title`, `order`, `permalink`).
- Place tutorials under `content/{en|uz}/tutorial/{basic|intermediate|advanced}/`.
- Use locale-first permalinks only:
- English tutorial: `/en/tutorial/{slug}/`
- Uzbek tutorial: `/uz/tutorial/{slug}/`
- Use `lang: en` or `lang: uz` in localized pages/tutorials.
- Wrap code-related prose tokens in inline code (for example: `if`, `else`, `for`, `while`, `True`, `False`, `None`, `{}`).
- Add descriptive image `alt` text.
- Verify internal links and JSON-LD output with `npm run check`.
- Keep localized repository docs under `docs/i18n/uz/` (these are not website pages).

## Pull Request Checklist

Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ npm run build
npm run check
```

## Content Structure

- English homepage: `content/en/index.md` (`/en/`)
- Uzbek homepage: `content/uz/index.md` (`/uz/`)
- English tutorials: `content/en/tutorial/{basic,intermediate,advanced}/*.md`
- Uzbek tutorials: `content/uz/tutorial/{basic,intermediate,advanced}/*.md`

Canonical tutorial URLs:

- English: `/en/tutorial/{slug}/`
- Uzbek: `/uz/tutorial/{slug}/`

Root `/` redirects to `/en/`.

Uzbek repository docs are stored in `docs/i18n/uz/` (repo-only, not website pages).

### License

Belajarpython is [MIT licensed](./LICENSE).
72 changes: 55 additions & 17 deletions _includes/footer.njk
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{% set footerVariant = footerVariant or 'compact' %}
{% set currentUrl = page.url or '/' %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') or currentUrl.startsWith('/tutorial/uz/') %}
{% set homeUrl = '/uz/' if isUzPage else '/' %}
{% set tutorialUrl = '/tutorial/uz/what-is-python/' if isUzPage else '/tutorial/en/what-is-python/' %}
{% set githubUrl = 'https://github.com/codeuzhub/python-lessons' %}
{% set contributeUrl = githubUrl + '/blob/main/.github/CONTRIBUTING.md' %}
{% set issueUrl = githubUrl + '/issues' %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') %}
{% set homeUrl = '/uz/' if isUzPage else '/en/' %}
{% set tutorialUrl = '/uz/tutorial/what-is-python/' if isUzPage else '/en/tutorial/what-is-python/' %}
{% set githubRepoUrl = 'https://github.com/codeuzhub/python-lessons' %}
{% set githubProfileUrl = 'https://github.com/yakhyo' %}
{% set linkedinUrl = 'https://www.linkedin.com/in/y-valikhujaev/' %}
{% set youtubeUrl = 'https://www.youtube.com/@codeuz' %}
{% set telegramUrl = 'https://t.me/valikhujaev' %}
{% set contributeUrl = githubRepoUrl + '/blob/main/.github/CONTRIBUTING.md' %}
{% set issueUrl = githubRepoUrl + '/issues' %}
{% set siteLabel = "Python Lessons" %}
{% set taglineText = "Python tutorials in English and Uzbek." if not isUzPage else "Python darslari ingliz va o'zbek tillarida." %}
{% set quickLinksLabel = "Quick Links" if not isUzPage else "Tezkor havolalar" %}
Expand All @@ -18,6 +22,8 @@
{% set issueLabel = "Report an Issue" if not isUzPage else "Muammo haqida yozing" %}
{% set compactTutorialLabel = "Tutorial Index" if not isUzPage else "Darslar ro'yxati" %}
{% set licenseLabel = "MIT Licensed" if not isUzPage else "MIT litsenziyasi asosida" %}
{% set brandLabel = "code uz" %}
{% set compactFooterShowLinks = compactFooterShowLinks if compactFooterShowLinks is defined else true %}

{% if footerVariant == 'full' %}
<footer class="mt-12 border-t border-slate-200 bg-slate-900 text-slate-200">
Expand Down Expand Up @@ -49,34 +55,66 @@
<h2 class="text-xs font-semibold uppercase tracking-wide text-slate-400">{{ projectLabel }}</h2>
<ul class="mt-3 space-y-2 text-sm">
<li>
<a href="{{ githubUrl }}" class="text-slate-200 no-underline hover:text-secondary-400">{{ joinGithubLabel }}</a>
<a href="{{ githubRepoUrl }}" class="text-slate-200 no-underline hover:text-secondary-400">{{ joinGithubLabel }}</a>
</li>
<li>
<a href="{{ contributeUrl }}" class="text-slate-200 no-underline hover:text-secondary-400">{{ contributeLabel }}</a>
</li>
<li>
<a href="{{ issueUrl }}" class="text-slate-200 no-underline hover:text-secondary-400">{{ issueLabel }}</a>
</li>
<li>
<a href="{{ '/LICENSE' | url }}" class="text-slate-200 no-underline hover:text-secondary-400">{{ licenseLabel }}</a>
</li>
</ul>
</div>
</div>
<div class="mt-8 flex flex-col gap-2 border-t border-slate-800 pt-4 text-xs text-slate-400 md:flex-row md:items-center md:justify-between">
<p>© 2026 {{ siteLabel }}</p>
<a href="{{ '/LICENSE' | url }}" class="text-slate-300 no-underline hover:text-secondary-400">{{ licenseLabel }}</a>
<p>© 2026 {{ siteLabel }} | {{ brandLabel }}</p>
<div class="flex items-center gap-3 text-base">
<a href="{{ githubProfileUrl }}" class="text-slate-300 no-underline hover:text-secondary-400" rel="noopener noreferrer" aria-label="GitHub">
<i class="fa-brands fa-github" aria-hidden="true"></i>
</a>
<a href="{{ linkedinUrl }}" class="text-slate-300 no-underline hover:text-secondary-400" rel="noopener noreferrer" aria-label="LinkedIn">
<i class="fa-brands fa-linkedin-in" aria-hidden="true"></i>
</a>
<a href="{{ youtubeUrl }}" class="text-slate-300 no-underline hover:text-secondary-400" rel="noopener noreferrer" aria-label="YouTube">
<i class="fa-brands fa-youtube" aria-hidden="true"></i>
</a>
<a href="{{ telegramUrl }}" class="text-slate-300 no-underline hover:text-secondary-400" rel="noopener noreferrer" aria-label="Telegram">
<i class="fa-brands fa-telegram" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
</footer>
{% else %}
<footer class="mt-10 border-t border-slate-200 bg-white">
<div class="mx-auto max-w-4xl px-4 py-6 text-sm md:px-6">
<div class="flex flex-wrap items-center gap-x-5 gap-y-2">
<a href="{{ tutorialUrl | url }}" class="text-slate-700 no-underline hover:text-primary-500">{{ compactTutorialLabel }}</a>
<a href="{{ githubUrl }}" class="text-slate-700 no-underline hover:text-primary-500">GitHub</a>
<a href="{{ issueUrl }}" class="text-slate-700 no-underline hover:text-primary-500">{{ issueLabel }}</a>
</div>
<div class="mt-4 flex flex-col gap-2 border-t border-slate-200 pt-3 text-xs text-slate-500 md:flex-row md:items-center md:justify-between">
<p>© 2026 {{ siteLabel }}</p>
<a href="{{ '/LICENSE' | url }}" class="text-slate-500 no-underline hover:text-primary-500">{{ licenseLabel }}</a>
{% if compactFooterShowLinks %}
<div class="flex flex-wrap items-center gap-x-5 gap-y-2">
<a href="{{ tutorialUrl | url }}" class="text-slate-700 no-underline hover:text-primary-500">{{ compactTutorialLabel }}</a>
<a href="{{ githubProfileUrl }}" class="text-slate-700 no-underline hover:text-primary-500" rel="noopener noreferrer">GitHub</a>
<a href="{{ issueUrl }}" class="text-slate-700 no-underline hover:text-primary-500">{{ issueLabel }}</a>
<a href="{{ '/LICENSE' | url }}" class="text-slate-700 no-underline hover:text-primary-500">{{ licenseLabel }}</a>
</div>
{% endif %}
<div class="{{ 'mt-4 border-t border-slate-200 pt-3' if compactFooterShowLinks else '' }} flex flex-col gap-2 text-xs text-slate-500 md:flex-row md:items-center md:justify-between">
<p>© 2026 {{ siteLabel }} | {{ brandLabel }}</p>
<div class="flex items-center gap-3 text-base text-slate-500">
<a href="{{ githubProfileUrl }}" class="text-slate-500 no-underline hover:text-primary-500" rel="noopener noreferrer" aria-label="GitHub">
<i class="fa-brands fa-github" aria-hidden="true"></i>
</a>
<a href="{{ linkedinUrl }}" class="text-slate-500 no-underline hover:text-primary-500" rel="noopener noreferrer" aria-label="LinkedIn">
<i class="fa-brands fa-linkedin-in" aria-hidden="true"></i>
</a>
<a href="{{ youtubeUrl }}" class="text-slate-500 no-underline hover:text-primary-500" rel="noopener noreferrer" aria-label="YouTube">
<i class="fa-brands fa-youtube" aria-hidden="true"></i>
</a>
<a href="{{ telegramUrl }}" class="text-slate-500 no-underline hover:text-primary-500" rel="noopener noreferrer" aria-label="Telegram">
<i class="fa-brands fa-telegram" aria-hidden="true"></i>
</a>
</div>
</div>
</div>
</footer>
Expand Down
2 changes: 1 addition & 1 deletion _includes/landing.njk
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<section id="materials" class="py-8 md:py-14">
{% set currentUrl = page.url or '/' %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') or currentUrl.startsWith('/tutorial/uz/') %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') %}
{% set defaultTutorialList = collections.tutorialsUz if (isUzPage and collections.tutorialsUz) else collections.tutorials %}
{% set tutorialList = tutorialItems or defaultTutorialList %}
<div class="mx-auto max-w-4xl px-4 md:px-10">
Expand Down
20 changes: 11 additions & 9 deletions _includes/navbar.njk
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
{% set currentUrl = page.url or '/' %}
{% set isEnTutorial = currentUrl.startsWith('/tutorial/en/') %}
{% set isUzTutorial = currentUrl.startsWith('/tutorial/uz/') %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') or isUzTutorial %}
{% set enUrl = currentUrl | replace('/tutorial/uz/', '/tutorial/en/') if isUzTutorial else '/' %}
{% set uzUrl = currentUrl | replace('/tutorial/en/', '/tutorial/uz/') if isEnTutorial else '/uz/' %}
{% set homeUrl = '/uz/' if isUzPage else '/' %}
{% set tutorialUrl = '/tutorial/uz/what-is-python/' if isUzPage else '/tutorial/en/what-is-python/' %}
{% set isEnTutorial = currentUrl.startsWith('/en/tutorial/') %}
{% set isUzTutorial = currentUrl.startsWith('/uz/tutorial/') %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') %}
{% set enUrl = currentUrl | replace('/uz/tutorial/', '/en/tutorial/') if isUzTutorial else '/en/' %}
{% set uzUrl = currentUrl | replace('/en/tutorial/', '/uz/tutorial/') if isEnTutorial else '/uz/' %}
{% set homeUrl = '/uz/' if isUzPage else '/en/' %}
{% set tutorialUrl = '/uz/tutorial/what-is-python/' if isUzPage else '/en/tutorial/what-is-python/' %}
{% set tutorialLabel = "Darslar" if isUzPage else "Tutorial" %}
{% set githubLabel = "GitHub'da qo'shiling" if isUzPage else "Join on Github" %}
{% set languageSwitchUrl = '/uz/' if not isUzPage else '/' %}
{% set languageSwitchUrl = '/uz/' if not isUzPage else '/en/' %}
{% if currentUrl == '/' %}
{% set languageSwitchUrl = '/uz/' %}
{% elif currentUrl == '/en/' %}
{% set languageSwitchUrl = '/uz/' %}
{% elif currentUrl == '/uz/' %}
{% set languageSwitchUrl = '/' %}
{% set languageSwitchUrl = '/en/' %}
{% elif isEnTutorial %}
{% set languageSwitchUrl = uzUrl %}
{% elif isUzTutorial %}
Expand Down
93 changes: 93 additions & 0 deletions _includes/python-tool-runner.njk
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,31 @@
return preCaretRange.toString().length;
}

function getSelectionOffsets(container) {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) {
return null;
}

const range = selection.getRangeAt(0);
if (!container.contains(range.startContainer) || !container.contains(range.endContainer)) {
return null;
}

const startRange = range.cloneRange();
startRange.selectNodeContents(container);
startRange.setEnd(range.startContainer, range.startOffset);

const endRange = range.cloneRange();
endRange.selectNodeContents(container);
endRange.setEnd(range.endContainer, range.endOffset);

return {
start: startRange.toString().length,
end: endRange.toString().length
};
}

function setCaretOffset(container, offset) {
const safeOffset = Math.max(0, offset || 0);
const selection = window.getSelection();
Expand Down Expand Up @@ -267,6 +292,74 @@
editor.addEventListener('focus', () => {
editor.className = `${baseClassName} hljs`;
});
const applyEditorText = (nextSource, nextCaretOffset) => {
editor.textContent = nextSource;
renderHighlightedEditable(editor, editorLanguage, baseClassName);
setCaretOffset(editor, Math.min(nextCaretOffset, nextSource.length));
};

editor.addEventListener('keydown', (event) => {
const offsets = getSelectionOffsets(editor);
if (!offsets) {
return;
}

const tabText = ' ';
const source = editor.textContent || '';
const { start, end } = offsets;

if (event.key === 'Enter') {
event.preventDefault();
const lineStart = source.lastIndexOf('\n', Math.max(0, start - 1)) + 1;
const linePrefix = source.slice(lineStart, start);
const baseIndentMatch = linePrefix.match(/^[ \t]*/);
const baseIndent = baseIndentMatch ? baseIndentMatch[0] : '';
const shouldIncreaseIndent = /:\s*(#.*)?$/.test(linePrefix.trimEnd());
const insertText = `\n${baseIndent}${shouldIncreaseIndent ? tabText : ''}`;
const nextSource = source.slice(0, start) + insertText + source.slice(end);
applyEditorText(nextSource, start + insertText.length);
return;
}

if (event.key !== 'Tab') {
return;
}

event.preventDefault();

if (start !== end) {
const lineStart = source.lastIndexOf('\n', Math.max(0, start - 1)) + 1;
const rawLineEnd = source.indexOf('\n', end);
const lineEnd = rawLineEnd === -1 ? source.length : rawLineEnd;
const block = source.slice(lineStart, lineEnd);
const lines = block.split('\n');
const transformedLines = event.shiftKey
? lines.map((line) => line.replace(/^ {1,4}/, ''))
: lines.map((line) => `${tabText}${line}`);
const nextBlock = transformedLines.join('\n');
const nextSource = source.slice(0, lineStart) + nextBlock + source.slice(lineEnd);
applyEditorText(nextSource, lineStart + nextBlock.length);
return;
}

if (event.shiftKey) {
const lineStart = source.lastIndexOf('\n', Math.max(0, start - 1)) + 1;
const prefix = source.slice(lineStart, start);
const removable = prefix.match(/ {1,4}$/);
if (!removable) {
return;
}

const removeCount = removable[0].length;
const removeStart = start - removeCount;
const nextSource = source.slice(0, removeStart) + source.slice(start);
applyEditorText(nextSource, removeStart);
return;
}

const nextSource = source.slice(0, start) + tabText + source.slice(end);
applyEditorText(nextSource, start + tabText.length);
});
renderHighlightedEditable(editor, editorLanguage, baseClassName);

runBtn.addEventListener('click', async () => {
Expand Down
2 changes: 1 addition & 1 deletion _includes/sidebar-list.njk
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- Basic Level -->
{% set currentUrl = page.url or '/' %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') or currentUrl.startsWith('/tutorial/uz/') %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') %}
{% set defaultTutorialList = collections.tutorialsUz if (isUzPage and collections.tutorialsUz) else collections.tutorials %}
{% set tutorialList = tutorialItems or defaultTutorialList %}
<div class="flex items-center gap-2 px-4 py-3 bg-slate-50 mb-2 border border-slate-100">
Expand Down
3 changes: 2 additions & 1 deletion _layouts/base.njk
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<!DOCTYPE html>
{% set currentUrl = page.url or '/' %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') or currentUrl.startsWith('/tutorial/uz/') %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') %}
{% set tutorialItems = collections.tutorialsUz if (isUzPage and collections.tutorialsUz) else collections.tutorials %}
{% set useBrandIconFont = true %}
<html lang="{{ lang or 'en' }}" class="scroll-smooth">
{% include "head.njk" %}
<body class="overflow-x-hidden">
Expand Down
1 change: 1 addition & 0 deletions _layouts/tool-simple.njk
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
{% set useBrandIconFont = true %}
{% include "head.njk" %}
<body class="overflow-x-hidden bg-slate-50 dark:bg-slate-900">
{% include "navbar.njk" %}
Expand Down
3 changes: 1 addition & 2 deletions _layouts/tool-split.njk
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
{% set useBrandIconFont = true %}
{% include "head.njk" %}
<body class="h-full flex flex-col bg-slate-50 dark:bg-slate-900 overflow-hidden">
{% include "navbar.njk" %}
<main class="flex-1 flex flex-col h-full min-h-0 relative">
{{ content | safe }}
</main>
{% set footerVariant = 'compact' %}
{% include "footer.njk" %}
{% include "analytics.njk" %}
</body>
</html>
3 changes: 2 additions & 1 deletion _layouts/tutorial.njk
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!DOCTYPE html>
{% set currentUrl = page.url or '/' %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') or currentUrl.startsWith('/tutorial/uz/') %}
{% set isUzPage = (lang or '') == 'uz' or currentUrl == '/uz/' or currentUrl.startsWith('/uz/') %}
{% set tutorialItems = collections.tutorialsUz if (isUzPage and collections.tutorialsUz) else collections.tutorials %}
<html lang="{{ lang or 'en' }}">
{% set isTutorial = true %}
Expand Down Expand Up @@ -124,6 +124,7 @@
</main>
</div>
{% set footerVariant = 'compact' %}
{% set compactFooterShowLinks = false %}
{% include "footer.njk" %}
{% include "analytics.njk" %}
<script>
Expand Down
2 changes: 1 addition & 1 deletion assets/css/base.css

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion index.md → content/en/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
layout: home.njk
lang: en
title: Python Programming Tutorial From Basic to Advanced
description: Learn Python for free with complete tutorials. Python programming guide from basic to advanced for data science, machine learning, AI, and web development. Trusted by 100+ campuses & companies since 2015.
permalink: /
permalink: /en/
isHomepage: true
---
Loading