init at home
This commit is contained in:
@@ -0,0 +1,740 @@
|
||||
```markdown
|
||||
---
|
||||
title: JavaScript Tutorial (für Python-Erfahrene)
|
||||
tags: [javascript, tutorial, beginner, programming]
|
||||
created: 2026-03-27
|
||||
---
|
||||
|
||||
# JavaScript Tutorial (für Python-Erfahrene)
|
||||
|
||||
Ziel: Du lernst JavaScript von Grund auf, aber mit Fokus auf das, was du als Python-Erfahrener schnell einordnen kannst. Viele Beispiele sind so gestaltet, dass du sie 1:1 nachbauen und variieren kannst.
|
||||
|
||||
---
|
||||
|
||||
## Inhaltsverzeichnis
|
||||
1. [[#0 Wie führe ich JavaScript am besten aus]]
|
||||
2. [[#1 Grundsyntax & Variablen]]
|
||||
3. [[#2 Datentypen, Equality, Truthy/Falsy]]
|
||||
4. [[#3 Strings & Template Literals]]
|
||||
5. [[#4 Arrays (Listen) & Methoden]]
|
||||
6. [[#5 Objekte (Dictionaries) & JSON]]
|
||||
7. [[#6 Kontrollfluss (if, switch, Schleifen)]]
|
||||
8. [[#7 Funktionen (inkl. Arrow Functions)]]
|
||||
9. [[#8 Scope, Hoisting, Closures]]
|
||||
10. [[#9 Module (import/export)]]
|
||||
11. [[#10 Klassen & Prototypen]]
|
||||
12. [[#11 Fehlerbehandlung (try/catch)]]
|
||||
13. [[#12 Asynchronität: Event Loop, Promises, async/await]]
|
||||
14. [[#13 DOM: Webseiten manipulieren]]
|
||||
15. [[#14 Events (Click, Input, Delegation)]]
|
||||
16. [[#15 Fetch API (HTTP), APIs, JSON]]
|
||||
17. [[#16 Praktische Mini-Projekte]]
|
||||
18. [[#17 Tooling: npm, Vite, Linting (kurz)]]
|
||||
19. [[#18 Cheatsheet: Python vs. JavaScript]]
|
||||
|
||||
---
|
||||
|
||||
## 0) Wie führe ich JavaScript am besten aus?
|
||||
|
||||
### Option A — Browser-Konsole (schnell zum Experimentieren)
|
||||
1. Öffne eine Website (z. B. `about:blank`)
|
||||
2. DevTools öffnen:
|
||||
- Chrome/Edge: `F12` oder `Ctrl+Shift+I`
|
||||
- macOS: `Cmd+Opt+I`
|
||||
3. Tab **Console** → JavaScript eintippen.
|
||||
|
||||
Beispiel:
|
||||
```js
|
||||
console.log("Hello JS!");
|
||||
```
|
||||
|
||||
**Pro:** sofortiges Feedback
|
||||
**Contra:** nicht so gut für größere Projekte
|
||||
|
||||
---
|
||||
|
||||
### Option B — HTML + `<script>` direkt im HTML (einfach)
|
||||
Erstelle `index.html`:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>JS Tutorial</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hallo JavaScript</h1>
|
||||
|
||||
<script>
|
||||
console.log("JS läuft!");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Öffne die Datei im Browser (Doppelklick) und dann DevTools → Console.
|
||||
|
||||
---
|
||||
|
||||
### Option C — HTML + externes `.js` File (empfohlen)
|
||||
Das ist die gängigste Lern- und Projektstruktur.
|
||||
|
||||
`index.html`:
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>JS Tutorial</title>
|
||||
<script defer src="./main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hallo JavaScript</h1>
|
||||
<button id="btn">Klick mich</button>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
`main.js`:
|
||||
```js
|
||||
console.log("main.js geladen");
|
||||
|
||||
document.querySelector("#btn").addEventListener("click", () => {
|
||||
console.log("Button geklickt");
|
||||
});
|
||||
```
|
||||
|
||||
**Wichtig:** `defer` sorgt dafür, dass das HTML zuerst geparst wird, bevor dein JS läuft (sehr praktisch für DOM-Skripte).
|
||||
|
||||
---
|
||||
|
||||
### Option D — Node.js (JavaScript außerhalb des Browsers)
|
||||
Installiere Node.js (LTS). Dann im Terminal:
|
||||
|
||||
```bash
|
||||
node -v
|
||||
node
|
||||
```
|
||||
|
||||
Oder Datei:
|
||||
`app.js`:
|
||||
```js
|
||||
console.log("Hallo aus Node!");
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
node app.js
|
||||
```
|
||||
|
||||
**Merke:** Browser-JS hat DOM/Web-APIs, Node hat dafür File-System etc.
|
||||
|
||||
---
|
||||
|
||||
## 1) Grundsyntax & Variablen
|
||||
|
||||
### `let`, `const`, (vermeide `var`)
|
||||
```js
|
||||
let x = 10; // kann neu zugewiesen werden
|
||||
x = 11;
|
||||
|
||||
const y = 20; // darf nicht neu zugewiesen werden
|
||||
// y = 21; // TypeError
|
||||
|
||||
const obj = { a: 1 };
|
||||
obj.a = 2; // erlaubt! (Objektinhalt mutiert)
|
||||
// obj = { a: 3 }; // nicht erlaubt (Neuzuweisung)
|
||||
```
|
||||
|
||||
**Python-Vergleich:** `const` ist kein echtes Immutable wie `tuple`, sondern verhindert nur Reassignment der Variable.
|
||||
|
||||
---
|
||||
|
||||
## 2) Datentypen, Equality, Truthy/Falsy
|
||||
|
||||
### Primitive Typen
|
||||
- `number` (integers + floats)
|
||||
- `string`
|
||||
- `boolean`
|
||||
- `undefined`
|
||||
- `null`
|
||||
- `bigint`
|
||||
- `symbol`
|
||||
|
||||
```js
|
||||
let a; // undefined
|
||||
let b = null; // absichtlich "leer"
|
||||
```
|
||||
|
||||
### `==` vs `===` (wichtig)
|
||||
- `===` vergleicht **ohne** Typumwandlung → nutze das fast immer
|
||||
- `==` macht implizite Konvertierung → vermeide für Anfänger
|
||||
|
||||
```js
|
||||
0 == false // true (komisch)
|
||||
0 === false // false (gut/strikt)
|
||||
|
||||
"5" == 5 // true
|
||||
"5" === 5 // false
|
||||
```
|
||||
|
||||
### Truthy / Falsy
|
||||
Falsy sind u. a.: `false`, `0`, `""`, `null`, `undefined`, `NaN`
|
||||
|
||||
```js
|
||||
if ("") console.log("läuft nicht");
|
||||
if ("0") console.log("läuft"); // nicht leerer String ist truthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3) Strings & Template Literals
|
||||
|
||||
```js
|
||||
const name = "Ada";
|
||||
const age = 30;
|
||||
|
||||
console.log("Hallo " + name + ", du bist " + age);
|
||||
console.log(`Hallo ${name}, du bist ${age}`); // Template Literal
|
||||
```
|
||||
|
||||
Nützliche String-Methoden:
|
||||
```js
|
||||
"Hello".toUpperCase(); // "HELLO"
|
||||
" hi ".trim(); // "hi"
|
||||
"abc".includes("b"); // true
|
||||
"1,2,3".split(","); // ["1","2","3"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4) Arrays (Listen) & Methoden
|
||||
|
||||
```js
|
||||
const nums = [1, 2, 3];
|
||||
nums.push(4); // [1,2,3,4]
|
||||
nums.pop(); // entfernt letztes
|
||||
nums[0]; // 1
|
||||
```
|
||||
|
||||
### Iteration
|
||||
```js
|
||||
for (const n of nums) {
|
||||
console.log(n);
|
||||
}
|
||||
```
|
||||
|
||||
### `map`, `filter`, `reduce` (sehr wichtig)
|
||||
```js
|
||||
const doubled = nums.map(n => n * 2); // [2,4,6]
|
||||
const evens = nums.filter(n => n % 2 === 0); // [2]
|
||||
const sum = nums.reduce((acc, n) => acc + n, 0); // 6
|
||||
```
|
||||
|
||||
**Python-Vergleich:** `map/filter/reduce` ähnlich, aber in JS extrem häufig in UI/Frontend.
|
||||
|
||||
---
|
||||
|
||||
## 5) Objekte (Dictionaries) & JSON
|
||||
|
||||
### Objekte (Key-Value)
|
||||
```js
|
||||
const user = {
|
||||
name: "Ada",
|
||||
age: 30,
|
||||
isAdmin: true
|
||||
};
|
||||
|
||||
console.log(user.name);
|
||||
console.log(user["age"]);
|
||||
```
|
||||
|
||||
### Dynamische Keys
|
||||
```js
|
||||
const key = "score";
|
||||
const obj = { [key]: 42 };
|
||||
console.log(obj.score); // 42
|
||||
```
|
||||
|
||||
### Object Destructuring
|
||||
```js
|
||||
const { name, age } = user;
|
||||
console.log(name, age);
|
||||
```
|
||||
|
||||
### Spread (Kopieren/Mergen)
|
||||
```js
|
||||
const a = { x: 1, y: 2 };
|
||||
const b = { ...a, y: 999, z: 3 }; // {x:1,y:999,z:3}
|
||||
```
|
||||
|
||||
### JSON
|
||||
```js
|
||||
const json = JSON.stringify(user);
|
||||
const parsed = JSON.parse(json);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6) Kontrollfluss (if, switch, Schleifen)
|
||||
|
||||
```js
|
||||
const n = 7;
|
||||
|
||||
if (n > 10) {
|
||||
console.log("groß");
|
||||
} else if (n > 5) {
|
||||
console.log("mittel");
|
||||
} else {
|
||||
console.log("klein");
|
||||
}
|
||||
```
|
||||
|
||||
### `switch`
|
||||
```js
|
||||
const role = "admin";
|
||||
|
||||
switch (role) {
|
||||
case "admin":
|
||||
console.log("Alles erlaubt");
|
||||
break;
|
||||
case "user":
|
||||
console.log("Standardrechte");
|
||||
break;
|
||||
default:
|
||||
console.log("Unbekannt");
|
||||
}
|
||||
```
|
||||
|
||||
### Schleifen
|
||||
```js
|
||||
for (let i = 0; i < 3; i++) console.log(i);
|
||||
|
||||
let i = 0;
|
||||
while (i < 3) {
|
||||
console.log(i);
|
||||
i++;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7) Funktionen (inkl. Arrow Functions)
|
||||
|
||||
### Funktionsdeklaration
|
||||
```js
|
||||
function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
### Function Expression
|
||||
```js
|
||||
const add2 = function (a, b) {
|
||||
return a + b;
|
||||
};
|
||||
```
|
||||
|
||||
### Arrow Function
|
||||
```js
|
||||
const add3 = (a, b) => a + b;
|
||||
```
|
||||
|
||||
### Default-Parameter
|
||||
```js
|
||||
function greet(name = "Welt") {
|
||||
return `Hallo ${name}`;
|
||||
}
|
||||
```
|
||||
|
||||
### Rest-Parameter
|
||||
```js
|
||||
function sum(...nums) {
|
||||
return nums.reduce((acc, n) => acc + n, 0);
|
||||
}
|
||||
sum(1, 2, 3); // 6
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8) Scope, Hoisting, Closures
|
||||
|
||||
### Block-Scope: `let/const`
|
||||
```js
|
||||
if (true) {
|
||||
let x = 1;
|
||||
}
|
||||
// console.log(x); // ReferenceError
|
||||
```
|
||||
|
||||
### Hoisting (vereinfachte Regel)
|
||||
- `var` wird “hochgezogen” → vermeiden
|
||||
- `let/const` existieren zwar “vorher”, sind aber in der **Temporal Dead Zone** → besser/strikter
|
||||
|
||||
### Closure
|
||||
```js
|
||||
function makeCounter() {
|
||||
let count = 0;
|
||||
return function () {
|
||||
count++;
|
||||
return count;
|
||||
};
|
||||
}
|
||||
|
||||
const c = makeCounter();
|
||||
c(); // 1
|
||||
c(); // 2
|
||||
```
|
||||
|
||||
**Python-Vergleich:** ähnlich wie inner functions mit captured variables.
|
||||
|
||||
---
|
||||
|
||||
## 9) Module (import/export)
|
||||
|
||||
**Browser-Module**: In HTML:
|
||||
```html
|
||||
<script type="module" src="./main.js"></script>
|
||||
```
|
||||
|
||||
`math.js`:
|
||||
```js
|
||||
export function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
export const PI = 3.14159;
|
||||
```
|
||||
|
||||
`main.js`:
|
||||
```js
|
||||
import { add, PI } from "./math.js";
|
||||
|
||||
console.log(add(2, 3));
|
||||
console.log(PI);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10) Klassen & Prototypen
|
||||
|
||||
### Klasse
|
||||
```js
|
||||
class User {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
greet() {
|
||||
return `Hi ${this.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
const u = new User("Ada");
|
||||
console.log(u.greet());
|
||||
```
|
||||
|
||||
### Vererbung
|
||||
```js
|
||||
class Admin extends User {
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.role = "admin";
|
||||
}
|
||||
}
|
||||
|
||||
const a = new Admin("Grace");
|
||||
```
|
||||
|
||||
**Merke:** JS ist prototyp-basiert; `class` ist syntactic sugar.
|
||||
|
||||
---
|
||||
|
||||
## 11) Fehlerbehandlung (try/catch)
|
||||
|
||||
```js
|
||||
try {
|
||||
JSON.parse("{ broken");
|
||||
} catch (err) {
|
||||
console.error("Parsing fehlgeschlagen:", err.message);
|
||||
} finally {
|
||||
console.log("läuft immer");
|
||||
}
|
||||
```
|
||||
|
||||
Eigene Fehler:
|
||||
```js
|
||||
function requirePositive(n) {
|
||||
if (n <= 0) throw new Error("n muss > 0 sein");
|
||||
return n;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12) Asynchronität: Event Loop, Promises, async/await
|
||||
|
||||
### Warum async?
|
||||
Im Browser soll UI nicht blockieren. In Node geht’s um I/O.
|
||||
|
||||
### Promise Basics
|
||||
```js
|
||||
const p = new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve("fertig"), 500);
|
||||
});
|
||||
|
||||
p.then(value => console.log(value));
|
||||
```
|
||||
|
||||
### `async/await` (empfohlen)
|
||||
```js
|
||||
function wait(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function run() {
|
||||
console.log("start");
|
||||
await wait(300);
|
||||
console.log("nach 300ms");
|
||||
}
|
||||
|
||||
run();
|
||||
```
|
||||
|
||||
### Parallel vs. sequenziell
|
||||
```js
|
||||
async function parallel() {
|
||||
const p1 = wait(200);
|
||||
const p2 = wait(300);
|
||||
await Promise.all([p1, p2]);
|
||||
console.log("beide fertig");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13) DOM: Webseiten manipulieren
|
||||
|
||||
### Elemente finden
|
||||
```js
|
||||
const h1 = document.querySelector("h1");
|
||||
const items = document.querySelectorAll("li");
|
||||
```
|
||||
|
||||
### Text/HTML ändern
|
||||
```js
|
||||
h1.textContent = "Neuer Titel";
|
||||
h1.innerHTML = "<em>Kursiv</em>"; // vorsichtig (XSS bei fremden Daten!)
|
||||
```
|
||||
|
||||
### Klassen und Styles
|
||||
```js
|
||||
h1.classList.add("highlight");
|
||||
h1.style.color = "tomato";
|
||||
```
|
||||
|
||||
### Elemente erstellen & anhängen
|
||||
```js
|
||||
const ul = document.querySelector("ul");
|
||||
const li = document.createElement("li");
|
||||
li.textContent = "Neues Item";
|
||||
ul.appendChild(li);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14) Events (Click, Input, Delegation)
|
||||
|
||||
`index.html`:
|
||||
```html
|
||||
<input id="name" placeholder="Name" />
|
||||
<button id="save">Speichern</button>
|
||||
<div id="out"></div>
|
||||
```
|
||||
|
||||
`main.js`:
|
||||
```js
|
||||
const input = document.querySelector("#name");
|
||||
const btn = document.querySelector("#save");
|
||||
const out = document.querySelector("#out");
|
||||
|
||||
btn.addEventListener("click", () => {
|
||||
out.textContent = `Hallo ${input.value}`;
|
||||
});
|
||||
```
|
||||
|
||||
### Event Delegation (praktisch bei Listen)
|
||||
```js
|
||||
document.querySelector("#list").addEventListener("click", (e) => {
|
||||
if (e.target.matches("li")) {
|
||||
console.log("geklickt:", e.target.textContent);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15) Fetch API (HTTP), APIs, JSON
|
||||
|
||||
### GET Request
|
||||
```js
|
||||
async function loadTodo() {
|
||||
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
|
||||
const data = await res.json();
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
loadTodo().catch(console.error);
|
||||
```
|
||||
|
||||
### POST Request
|
||||
```js
|
||||
async function createPost() {
|
||||
const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ title: "Hi", body: "Text", userId: 1 })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
console.log("created:", data);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16) Praktische Mini-Projekte
|
||||
|
||||
### Projekt 1: Counter
|
||||
`index.html`:
|
||||
```html
|
||||
<h1 id="count">0</h1>
|
||||
<button id="dec">-</button>
|
||||
<button id="inc">+</button>
|
||||
<button id="reset">reset</button>
|
||||
<script defer src="./main.js"></script>
|
||||
```
|
||||
|
||||
`main.js`:
|
||||
```js
|
||||
let count = 0;
|
||||
|
||||
const el = document.querySelector("#count");
|
||||
const render = () => (el.textContent = String(count));
|
||||
|
||||
document.querySelector("#inc").addEventListener("click", () => {
|
||||
count++;
|
||||
render();
|
||||
});
|
||||
document.querySelector("#dec").addEventListener("click", () => {
|
||||
count--;
|
||||
render();
|
||||
});
|
||||
document.querySelector("#reset").addEventListener("click", () => {
|
||||
count = 0;
|
||||
render();
|
||||
});
|
||||
|
||||
render();
|
||||
```
|
||||
|
||||
### Projekt 2: Todo-Liste (minimal)
|
||||
`index.html`:
|
||||
```html
|
||||
<input id="todo" placeholder="Neues Todo" />
|
||||
<button id="add">Add</button>
|
||||
<ul id="list"></ul>
|
||||
<script defer src="./main.js"></script>
|
||||
```
|
||||
|
||||
`main.js`:
|
||||
```js
|
||||
const input = document.querySelector("#todo");
|
||||
const btn = document.querySelector("#add");
|
||||
const list = document.querySelector("#list");
|
||||
|
||||
btn.addEventListener("click", () => {
|
||||
const text = input.value.trim();
|
||||
if (!text) return;
|
||||
|
||||
const li = document.createElement("li");
|
||||
li.textContent = text;
|
||||
list.appendChild(li);
|
||||
|
||||
input.value = "";
|
||||
input.focus();
|
||||
});
|
||||
|
||||
// Klick zum Entfernen (Delegation)
|
||||
list.addEventListener("click", (e) => {
|
||||
if (e.target.matches("li")) e.target.remove();
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17) Tooling: npm, Vite, Linting (kurz)
|
||||
|
||||
Wenn du über “nur HTML+JS” hinaus willst (Module, Bundling, Dev-Server):
|
||||
|
||||
### Vite Setup
|
||||
```bash
|
||||
npm create vite@latest my-app
|
||||
cd my-app
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Das erstellt ein modernes Dev-Setup mit Hot Reload.
|
||||
|
||||
### Linting (optional)
|
||||
- ESLint hilft, typische Fehler früh zu finden.
|
||||
- Prettier formatiert Code automatisch.
|
||||
|
||||
---
|
||||
|
||||
## 18) Cheatsheet: Python vs. JavaScript
|
||||
|
||||
### Datenstrukturen
|
||||
- Python `list` ≈ JS `Array`
|
||||
- Python `dict` ≈ JS `Object` oder `Map`
|
||||
- Python `None` ≈ JS `null` (oder `undefined` je nach Kontext)
|
||||
|
||||
### Funktionen
|
||||
- Python: `def f(a=1): ...`
|
||||
- JS: `function f(a = 1) { ... }` oder `(a = 1) => ...`
|
||||
|
||||
### Strings
|
||||
- Python: `f"{x}"`
|
||||
- JS: `` `${x}` ``
|
||||
|
||||
### Imports
|
||||
- Python: `import x`
|
||||
- JS (ESM): `import x from "./x.js"` / `import {a} from ...`
|
||||
|
||||
### Iteration
|
||||
- Python: `for x in xs:`
|
||||
- JS: `for (const x of xs) { ... }`
|
||||
|
||||
### Async
|
||||
- Python: `async def`, `await`
|
||||
- JS: `async function`, `await` (sehr ähnlich im Feeling, andere Runtime-Details)
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte (Empfehlung)
|
||||
1. Baue die Mini-Projekte nach und verändere sie (z. B. Todo: “erledigt” toggle statt löschen).
|
||||
2. Übe `map/filter/reduce` auf Arrays mit echten Daten (z. B. aus `fetch`).
|
||||
3. Wenn du Web-Apps willst: lerne danach ein Framework (React/Vue/Svelte) – aber erst, wenn DOM + async sitzen.
|
||||
|
||||
---
|
||||
|
||||
## Offene Fragen (damit ich das Tutorial besser anpassen kann)
|
||||
- Willst du primär **Frontend im Browser** lernen oder auch **Node.js Backend**?
|
||||
- Arbeitest du lieber mit **Vanilla JS** (ohne Framework) oder willst du schnell Richtung **React/Vue**?
|
||||
```
|
||||
|
||||
Wenn du willst, kann ich daraus auch mehrere Obsidian-Notizen machen (z. B. „JS Basics“, „DOM“, „Async“, „Mini-Projekte“) und dir eine sinnvolle Link-Struktur (MOC) anlegen.
|
||||
Reference in New Issue
Block a user