feat: WCAG-AAA-Kontraste, Cavendish-Skin, Deploy-Pipeline

Barrierefreiheit (cavendish.css / header.css):
- Header auf dunkles Teal (#003535) umgestellt, Titel weiß
- Tab-Buttons an dunklen Header angepasst (Kontrast 12:1 AAA)
- Status-Boxen (.successbox/.errorbox/.warningbox) von <2:1 auf 7-8:1 angehoben
- Grau-Texte (#888, #808080, #7d7d7d) auf #595959 (7:1 AAA) vereinheitlicht
- Aktive Links, Footer, Nav-Labels auf WCAG-AAA-Kontrast gebracht
- header.css in skin.json registriert (war nie geladen)
- Cavendish-Skin vollständig versioniert

Deploy-Infrastruktur:
- .forgejo/workflows/deploy.yml: Auto-Deploy via Forgejo Actions bei Push auf main
- scripts/deploy.ps1: manuelles Windows-Deploy (WSL rsync / scp-Fallback)
- mediawiki/LocalSettings.production.example.php: Produktions-Konfigurationsvorlage
- .deploy.example.env: Verbindungsdaten-Template
- package.json: deploy / deploy:all / deploy:initial Skripte ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sascha 2026-06-04 16:25:01 +02:00
parent c0fb067595
commit 60ecb5962d
95 changed files with 5617 additions and 3 deletions

13
.deploy.example.env Normal file
View File

@ -0,0 +1,13 @@
# Deployment-Konfiguration für GPLX
# Kopiere diese Datei als .deploy.env und fülle deine Werte ein.
# .deploy.env wird NICHT ins Git-Repository übernommen.
# SSH-Verbindung
DEPLOY_HOST=deine-subdomain.bitpalast.de
DEPLOY_USER=dein-ssh-user
# Absoluter Pfad zu mediawiki/ auf dem Server
DEPLOY_MW_PATH=/var/www/html/gplx/mediawiki
# Absoluter Pfad zum Projektverzeichnis (eine Ebene über mediawiki/)
DEPLOY_PROJECT_PATH=/var/www/html/gplx

View File

@ -0,0 +1,38 @@
name: Deploy to Bitpalast
# Triggert nur wenn relevante Dateien geändert werden
on:
push:
branches:
- main
paths:
- "mediawiki/skins/Cavendish/**"
- "scripts/templates/**"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: SSH-Key einrichten
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts
- name: Cavendish-Skin deployen
run: |
rsync -az --delete \
mediawiki/skins/Cavendish/ \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_MW_PATH }}/skins/Cavendish/"
- name: Wiki-Templates deployen
if: ${{ secrets.DEPLOY_PROJECT_PATH != '' }}
run: |
rsync -az \
scripts/templates/ \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PROJECT_PATH }}/scripts/templates/"

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ mediawiki/images/
mediawiki/vendor/
mediawiki/LocalSettings.php
.lighthouseci/
.deploy.env

82
CLAUDE.md Normal file
View File

@ -0,0 +1,82 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project overview
GPLX is a local-first MediaWiki 1.43 installation on Windows, purpose-built for an accessibility-focused content workflow. The Node/npm layer wraps PowerShell scripts for setup and tooling; the actual MediaWiki application lives in `./mediawiki/`.
## Commands
| Task | Command |
|---|---|
| Clone MediaWiki core | `npm run mw:init` |
| Install PHP (Composer) deps | `npm run mw:deps` |
| First-time setup (both above) | `npm run mw:setup` |
| Start dev server (PHP built-in, port 8080) | `npm run dev` |
| Run Pa11y accessibility checks | `npm run a11y:pa11y` |
| Run Lighthouse accessibility checks | `npm run a11y:lighthouse` |
| Run all accessibility checks | `npm run a11y:all` |
**Prerequisites:** Node.js 20+, PHP 8.1+ (XAMPP or WampServer, or PHP in PATH).
After first setup, open `http://127.0.0.1:8080/mw-config/index.php` to finish the MediaWiki installer.
## Architecture
```
gplx/
├── mediawiki/ # MediaWiki 1.43 core (cloned from Gerrit, branch REL1_43)
│ ├── LocalSettings.php # All site config and custom hooks live here
│ ├── extensions/ # 35 bundled extensions + SemanticMediaWiki 6.0.1 via Composer
│ └── skins/ # Cavendish (default), Vector, MonoBook, Timeless, MinervaNeue
├── scripts/ # PowerShell scripts (dev.ps1, mw-init.ps1, mw-deps.ps1)
│ └── templates/ # Wiki page source files (.wiki) — pushed via maintenance/run.php edit
├── tests/accessibility/ # Pa11y (pa11y.config.json) + Lighthouse (lighthouserc.json) configs
├── docs/ # Project docs (ACCESSIBILITY_WORKFLOW.md, INSTALLATION_WINDOWS.md)
└── package.json # npm entry point — all commands delegate to scripts/
```
### Key architectural decisions
**LocalSettings.php is the customization layer.** All MediaWiki configuration, hook registrations, and inline CSS lives in `mediawiki/LocalSettings.php`. There is no separate plugin or theme source tree — customizations are PHP hooks injected at startup.
**Approval workflow via ApprovedRevs.** Pages require explicit admin/reviewer approval before public visibility. A `BeforePageDisplay` hook enforces this: unapproved pages are hidden from non-creators and from the public; creators see a pending-review notice. Roles: `sysop`, `bureaucrat`, `reviewer` (the last is assignable by bureaucrats).
**Anonymous editing is disabled.** `$wgGroupPermissions["*"]["edit"] = false` — only registered users can edit.
**Default skin is Cavendish.** Loaded with `wfLoadSkin('Cavendish')`.
**PHP built-in server for local dev.** `dev.ps1` uses `php -S 127.0.0.1:8080 -t ./mediawiki`. It probes XAMPP/WampServer paths if `php` is not in PATH.
**composer.phar lives at the project root**, not inside `mediawiki/`. The `mw-deps.ps1` script downloads it on first run if absent.
**Semantic MediaWiki (SMW) 6.0.1 is installed.** Enabled via `enableSemantics('127.0.0.1:8080')` in `LocalSettings.php`. SMW adds 39 database tables (`smw_*`) and enables structured property annotations and `{{#ask:}}` queries across all wiki pages.
**Template:RegDocument annotates SMW properties.** Every page using `{{RegDocument}}` writes nine structured properties (Scope, Status, Area of validity, etc.) via `{{#set:}}`. These power the automatic queries on `Document_Index` and the category pages.
**Wiki page sources live in `scripts/templates/`.** All template and infrastructure page content is maintained as `.wiki` files and pushed via `php mediawiki/maintenance/run.php edit`. To update a wiki page, edit the `.wiki` file and re-run the push command:
```powershell
Get-Content scripts/templates/<file>.wiki -Raw | D:\xampp\php\php.exe mediawiki/maintenance/run.php edit --user "Gxplex admin" --summary "..." "<Page title>"
```
**Navigation is two-layered.** The sidebar (MediaWiki:Sidebar) links to `Document_Index` and `Special:Ask`. Category pages (e.g. `Category:Medical Devices`) embed `{{#ask:}}` queries showing all pages in that category with their metadata.
## SMW maintenance commands
| Task | Command |
|---|---|
| Initial SMW DB setup | `php mediawiki/extensions/SemanticMediaWiki/maintenance/setupStore.php --nochecks` |
| Rebuild SMW data index | `php mediawiki/extensions/SemanticMediaWiki/maintenance/rebuildData.php` |
| Rebuild property statistics | `php mediawiki/extensions/SemanticMediaWiki/maintenance/rebuildPropertyStatistics.php` |
Run `rebuildData.php` after bulk-importing pages via the maintenance script, or after updating a template that contains `{{#set:}}`.
## Accessibility baseline
- Required: WCAG 2.1 AA
- Stretch target: WCAG 2.1 AAA where practical
- Checks must pass after any template, style, or interaction change: `npm run a11y:all`
- Pa11y targets `http://127.0.0.1:8080/` and `/index.php/Main_Page` (WCAG2AA profile)
- Lighthouse asserts minimum accessibility score of 0.9 (warns, does not fail hard)

View File

@ -35,7 +35,59 @@ Open http://127.0.0.1:8080 and complete the MediaWiki web installer.
After installer completion, place generated LocalSettings.php in mediawiki/.
## 6. Run accessibility checks
## 6. Install Semantic MediaWiki
```powershell
php .\composer.phar --working-dir=mediawiki require mediawiki/semantic-media-wiki "~6.0" --prefer-dist --optimize-autoloader
```
Add to `mediawiki/LocalSettings.php` (after `wfLoadSkin`):
```php
wfLoadExtension( 'SemanticMediaWiki' );
enableSemantics( '127.0.0.1:8080' );
```
Run the SMW database setup:
```powershell
php mediawiki/extensions/SemanticMediaWiki/maintenance/setupStore.php --nochecks
```
## 7. Push wiki infrastructure pages
Templates, the sidebar, the document index, and category pages are maintained as `.wiki` files in `scripts/templates/`. Push them once after a fresh install:
```powershell
$php = "D:\xampp\php\php.exe"
$mw = "mediawiki\maintenance\run.php"
$usr = "Gxplex admin" # replace with your admin username
$pages = @{
"Template:RegDocument" = "scripts\templates\RegDocument.wiki"
"Template:Reference" = "scripts\templates\Reference.wiki"
"MediaWiki:Sidebar" = "scripts\templates\Sidebar.wiki"
"Document Index" = "scripts\templates\DocumentIndex.wiki"
"Category:Medical Devices" = "scripts\templates\Cat_MedicalDevices.wiki"
"Category:In Vitro Diagnostics" = "scripts\templates\Cat_IVD.wiki"
"Category:Clinical" = "scripts\templates\Cat_Clinical.wiki"
"Category:Post-Market Surveillance" = "scripts\templates\Cat_PostMarket.wiki"
"Category:Labelling" = "scripts\templates\Cat_Labelling.wiki"
"Main_Page" = "scripts\templates\MainPage.wiki"
}
foreach ($title in $pages.Keys) {
Get-Content $pages[$title] -Raw | & $php $mw edit --user $usr --summary "Initial setup" $title
}
```
Then rebuild the SMW data index:
```powershell
php mediawiki/extensions/SemanticMediaWiki/maintenance/rebuildData.php
```
## 8. Run accessibility checks
```bash
npm run a11y:all

View File

@ -0,0 +1,79 @@
<?php
# LocalSettings.php Produktionskonfiguration
# Lege diese Datei als LocalSettings.php auf dem Server ab.
# NICHT ins Git-Repository einchecken (.gitignore beachten).
if ( !defined( 'MEDIAWIKI' ) ) {
exit;
}
$wgSitename = "GxPlex";
$wgScriptPath = "";
# Subdomain deiner Bitpalast-Instanz (https empfohlen)
$wgServer = "https://DEINE-SUBDOMAIN.bitpalast.de";
$wgResourceBasePath = $wgScriptPath;
$wgLogos = [
'1x' => "$wgResourceBasePath/resources/assets/regulatory_wiki_icon_medical.svg",
'icon' => "$wgResourceBasePath/resources/assets/regulatory_wiki_icon_medical.svg",
];
# E-Mail (Zugangsdaten deines Mailservers eintragen)
$wgEnableEmail = true;
$wgEnableUserEmail = true;
$wgEmergencyContact = "admin@deine-domain.de";
$wgPasswordSender = "wiki@deine-domain.de";
$wgEnotifUserTalk = false;
$wgEnotifWatchlist = false;
$wgEmailAuthentication = true;
# Datenbankverbindung (Zugangsdaten von Bitpalast-Kundencenter)
$wgDBtype = "mysql";
$wgDBserver = "localhost";
$wgDBname = "DATENBANKNAME";
$wgDBuser = "DATENBANKBENUTZER";
$wgDBpassword = "DATENBANKPASSWORT";
$wgDBprefix = "";
$wgDBssl = false;
# Cache
$wgMainCacheType = CACHE_NONE;
$wgMemCachedServers = [];
# Uploads
$wgEnableUploads = true;
$wgUseInstantCommons = false;
$wgPingback = false;
$wgLanguageCode = "en";
$wgLocaltimezone = "Europe/Berlin";
# Neuen Secret Key generieren: php maintenance/run.php generateSecretKey
$wgSecretKey = "NEUEN_KEY_GENERIEREN";
$wgAuthenticationTokenVersion = "1";
$wgUpgradeKey = "NEUEN_UPGRADE_KEY_GENERIEREN";
# Rechte
$wgRightsPage = "";
$wgRightsUrl = "https://creativecommons.org/licenses/by/4.0/";
$wgRightsText = "Creative Commons Attribution";
$wgRightsIcon = "$wgResourceBasePath/resources/assets/licenses/cc-by.png";
$wgDiff3 = "";
# Skin
$wgDefaultSkin = "Cavendish";
wfLoadSkin( 'Cavendish' );
# Fehlerdetails in Produktion deaktivieren
$wgShowExceptionDetails = false;
# === CUSTOM CONFIGURATION ===
# Ab hier: identisch mit lokaler LocalSettings.php (Hooks, Permissions, SMW etc.)
# Den Abschnitt ab "// === Approved Revisions Configuration ===" aus der
# lokalen LocalSettings.php hierher kopieren.
#
# Wichtig: enableSemantics() muss die Produktions-URL verwenden:
# enableSemantics( 'DEINE-SUBDOMAIN.bitpalast.de' );

View File

@ -85,7 +85,8 @@
"wikimedia/wait-condition-loop": "2.0.2",
"wikimedia/wrappedstring": "4.0.1",
"wikimedia/xmp-reader": "0.10.2",
"zordius/lightncandy": "1.2.6"
"zordius/lightncandy": "1.2.6",
"mediawiki/semantic-media-wiki": "~6.0"
},
"require-dev": {
"composer/spdx-licenses": "1.5.8",

View File

@ -1,3 +1,6 @@
!README
!.gitignore
!/.vsls.json
# Composer-managed extensions (installed via mediawiki/composer.json)
SemanticMediaWiki/

View File

@ -0,0 +1,37 @@
<svg width="135" height="145" viewBox="0 0 145 145" role="img" xmlns="http://www.w3.org/2000/svg" style="">
<title style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Regulatory Wiki Icon Medical</title>
<desc style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Square icon with a structured regulatory node network on a clinical teal-green background.</desc>
<rect width="135" height="135" rx="22" fill="#006B6B" style="fill:rgb(0, 107, 107);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="42" y1="42" x2="67" y2="67" stroke="#A8D8D8" stroke-width="1.5" stroke-dasharray="3 2.5" style="fill:rgb(0, 0, 0);stroke:rgb(168, 216, 216);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-dasharray:3px, 2.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="93" y1="42" x2="68" y2="67" stroke="#A8D8D8" stroke-width="1.5" stroke-dasharray="3 2.5" style="fill:rgb(0, 0, 0);stroke:rgb(168, 216, 216);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-dasharray:3px, 2.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="42" y1="93" x2="67" y2="68" stroke="#E8F4F4" stroke-width="1.5" stroke-dasharray="3 2.5" style="fill:rgb(0, 0, 0);stroke:rgb(232, 244, 244);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-dasharray:3px, 2.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="93" y1="93" x2="68" y2="68" stroke="#E8F4F4" stroke-width="1.5" stroke-dasharray="3 2.5" style="fill:rgb(0, 0, 0);stroke:rgb(232, 244, 244);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-dasharray:3px, 2.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="42" y1="42" x2="93" y2="42" stroke="#A8D8D8" stroke-width="1" stroke-dasharray="3 3" opacity="0.4" style="fill:rgb(0, 0, 0);stroke:rgb(168, 216, 216);color:rgb(255, 255, 255);stroke-width:1px;stroke-dasharray:3px, 3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.4;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<line x1="42" y1="93" x2="93" y2="93" stroke="#E8F4F4" stroke-width="1" stroke-dasharray="3 3" opacity="0.4" style="fill:rgb(0, 0, 0);stroke:rgb(232, 244, 244);color:rgb(255, 255, 255);stroke-width:1px;stroke-dasharray:3px, 3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.4;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="28" y="28" width="28" height="28" rx="6" fill="#004F4F" style="fill:rgb(0, 79, 79);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="79" y="28" width="28" height="28" rx="6" fill="#004F4F" style="fill:rgb(0, 79, 79);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="28" y="79" width="28" height="28" rx="6" fill="#004F4F" style="fill:rgb(0, 79, 79);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="79" y="79" width="28" height="28" rx="6" fill="#004F4F" style="fill:rgb(0, 79, 79);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="52" y="52" width="31" height="31" rx="7" fill="#FFFFFF" opacity="0.95" style="fill:rgb(255, 255, 255);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.95;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="63" y="57" width="9" height="19" rx="2" fill="#006B6B" style="fill:rgb(0, 107, 107);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="58" y="62" width="19" height="9" rx="2" fill="#006B6B" style="fill:rgb(0, 107, 107);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<circle cx="42" cy="42" r="3.5" fill="#C8E8E8" style="fill:rgb(200, 232, 232);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<circle cx="93" cy="42" r="3.5" fill="#C8E8E8" style="fill:rgb(200, 232, 232);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<circle cx="42" cy="93" r="3.5" fill="#E8F4F4" style="fill:rgb(232, 244, 244);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<circle cx="93" cy="93" r="3.5" fill="#E8F4F4" style="fill:rgb(232, 244, 244);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="37" y="37" width="7" height="1.5" rx="1" fill="#A8D8D8" opacity="0.7" style="fill:rgb(168, 216, 216);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.7;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="37" y="41" width="5" height="1.5" rx="1" fill="#A8D8D8" opacity="0.4" style="fill:rgb(168, 216, 216);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.4;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="88" y="37" width="7" height="1.5" rx="1" fill="#A8D8D8" opacity="0.7" style="fill:rgb(168, 216, 216);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.7;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="88" y="41" width="5" height="1.5" rx="1" fill="#A8D8D8" opacity="0.4" style="fill:rgb(168, 216, 216);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.4;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="37" y="88" width="7" height="1.5" rx="1" fill="#E8F4F4" opacity="0.7" style="fill:rgb(232, 244, 244);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.7;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="37" y="92" width="5" height="1.5" rx="1" fill="#E8F4F4" opacity="0.4" style="fill:rgb(232, 244, 244);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.4;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="88" y="88" width="7" height="1.5" rx="1" fill="#E8F4F4" opacity="0.7" style="fill:rgb(232, 244, 244);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.7;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<rect x="88" y="92" width="5" height="1.5" rx="1" fill="#E8F4F4" opacity="0.4" style="fill:rgb(232, 244, 244);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.4;font-family:&quot;Anthropic Sans&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -2,3 +2,4 @@
!/.gitignore
!/.vsls.json
!/README
!/Cavendish

168
mediawiki/skins/Cavendish/.gitignore vendored Normal file
View File

@ -0,0 +1,168 @@
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
.builds
*.dotCover
## TODO: If you have NuGet Package Restore enabled, uncomment this
#packages/
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# ReSharper is a .NET coding add-in
_ReSharper*
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Others
[Bb]in
[Oo]bj
sql
TestResults
*.Cache
ClientBin
stylecop.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
############
## Windows
############
# Windows image file caches
Thumbs.db
# Folder config file
Desktop.ini
/node_modules
/vendor
/composer.lock
/.eslintcache
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
# Mac crap
.DS_Store

View File

@ -0,0 +1,7 @@
[gerrit]
host=gerrit.wikimedia.org
port=29418
project=mediawiki/skins/Cavendish.git
track=1
defaultrebase=0
defaultbranch=master

View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -0,0 +1,542 @@
<?php
use MediaWiki\Html\Html;
use MediaWiki\Linker\Linker;
use MediaWiki\MediaWikiServices;
use MediaWiki\ResourceLoader\SkinModule;
class CavendishTemplate extends BaseTemplate {
public $skin;
/**
* Generate the toolbox, complete with all three old hooks
*
* @return string html
*/
protected function getToolboxBox() {
$html = '';
$template = $this;
return $this->getBox( 'tb',
$this->data['sidebar']['TOOLBOX'],
'toolbox'
);
}
/**
* Generate the languages box
*
* @return string html
*/
protected function getLanguageBox() {
$html = '';
if ( $this->data['language_urls'] !== false ) {
$html .= $this->getBox( 'lang', $this->data['language_urls'], 'otherlanguages' );
}
return $html;
}
/**
* Helper function for getPortlet
*
* Merge all provided css classes into a single array
* Account for possible different input methods matching what Html::element stuff takes
*
* @param string|array $class base portlet/body class
* @param string|array $extraClasses any extra classes to also include
*
* @return array all classes to apply
*/
protected function mergeClasses( $class, $extraClasses ) {
if ( !is_array( $class ) ) {
$class = [ $class ];
}
if ( !is_array( $extraClasses ) ) {
$extraClasses = [ $extraClasses ];
}
return array_merge( $class, $extraClasses );
}
/**
* Generates a block of navigation links with a header
*
* @param string $name
* @param array|string $content array of links for use with makeListItem, or a block of text
* @param null|string|array $msg
* @param array $setOptions random crap to rename/do/whatever
*
* @return string html
*/
protected function getPortlet( $name, $content, $msg = null, $setOptions = [] ) {
// random stuff to override with any provided options
$options = array_merge( [
// handle role=search a little differently
'role' => 'navigation',
'search-input-id' => 'searchInput',
// extra classes/ids
'id' => 'p-' . $name,
'class' => 'mw-portlet',
'extra-classes' => '',
'body-id' => null,
'body-class' => 'mw-portlet-body',
'body-extra-classes' => '',
// wrapper for individual list items
'text-wrapper' => [ 'tag' => 'span' ],
// old toolbox hook support (use: [ 'SkinTemplateToolboxEnd' => [ &$skin, true ] ])
'hooks' => '',
// option to stick arbitrary stuff at the beginning of the ul
'list-prepend' => ''
], $setOptions );
// Handle the different $msg possibilities
if ( $msg === null ) {
$msg = $name;
$msgParams = [];
} elseif ( is_array( $msg ) ) {
$msgString = array_shift( $msg );
$msgParams = $msg;
$msg = $msgString;
} else {
$msgParams = [];
}
$msgObj = $this->getMsg( $msg, $msgParams );
if ( $msgObj->exists() ) {
$msgString = $msgObj->parse();
} else {
$msgString = htmlspecialchars( $msg );
}
$labelId = Sanitizer::escapeIdForAttribute( "p-$name-label" );
if ( is_array( $content ) ) {
$contentText = Html::openElement( 'ul',
[ 'lang' => $this->get( 'userlang' ), 'dir' => $this->get( 'dir' ) ]
);
$contentText .= $options['list-prepend'];
foreach ( $content as $key => $item ) {
if ( is_array( $options['text-wrapper'] ) ) {
$contentText .= $this->makeListItem(
$key,
$item,
[ 'text-wrapper' => $options['text-wrapper'] ]
);
} else {
$contentText .= $this->makeListItem(
$key,
$item
);
}
}
$contentText .= Html::closeElement( 'ul' );
} else {
$contentText = $content;
}
// Special handling for role=search
$divOptions = [
'role' => $options['role'],
'class' => $this->mergeClasses( $options['class'], $options['extra-classes'] ),
'id' => Sanitizer::escapeIdForAttribute( $options['id'] ),
'title' => Linker::titleAttrib( $options['id'] )
];
if ( $options['role'] !== 'search' ) {
$divOptions['aria-labelledby'] = $labelId;
}
$labelOptions = [
'id' => $labelId,
'lang' => $this->get( 'userlang' ),
'dir' => $this->get( 'dir' )
];
if ( $options['role'] == 'search' ) {
$msgString = Html::rawElement( 'label', [ 'for' => $options['search-input-id'] ], $msgString );
}
$bodyDivOptions = [
'class' => $this->mergeClasses( $options['body-class'], $options['body-extra-classes'] )
];
if ( is_string( $options['body-id'] ) ) {
$bodyDivOptions['id'] = $options['body-id'];
}
$html = Html::rawElement( 'div', $divOptions,
Html::rawElement( 'h3', $labelOptions, $msgString ) .
Html::rawElement( 'div', $bodyDivOptions,
$contentText .
$this->getSkin()->getAfterPortlet( $name )
)
);
return $html;
}
/**
* Generate a sidebar box using getPortlet(); prefill some common stuff
*
* @param string $name
* @param array|string $contents
* @param-taint $contents escapes_htmlnoent
* @param null|string|array|bool $msg
* @param array $setOptions
*
* @return string html
*/
protected function getBox( $name, $contents, $msg = null, $setOptions = [] ) {
$options = array_merge( [
'class' => 'portlet',
'body-class' => 'pBody',
'text-wrapper' => ''
], $setOptions );
// Do some special stuff for the personal menu
if ( $name == 'personal' ) {
$prependiture = '';
// Extension:UniversalLanguageSelector order - T121793
if ( array_key_exists( 'uls', $contents ) ) {
$prependiture .= $this->makeListItem( 'uls', $contents['uls'] );
unset( $contents['uls'] );
}
if ( !$this->getSkin()->getUser()->isLoggedIn() &&
MediaWikiServices::getInstance()->getGroupPermissionsLookup()
->groupHasPermission( '*', 'edit' )
) {
$prependiture .= Html::rawElement(
'li',
[ 'id' => 'pt-anonuserpage' ],
$this->getMsg( 'notloggedin' )->escaped()
);
}
$options['list-prepend'] = $prependiture;
}
return $this->getPortlet( $name, $contents, $msg, $options );
}
/**
* Generate the search, using config options for buttons (?)
*
* @return string html
*/
protected function getSearchBox() {
$html = '';
$optionButtons = "\u{00A0} " . $this->makeSearchButton(
'fulltext',
[
'id' => 'mw-searchButton',
'class' => 'searchButton',
'value' => $this->getMsg( 'searchbutton' )->text()
]
);
$searchInputId = 'searchInput';
$searchForm = Html::rawElement( 'form', [
'action' => $this->get( 'wgScript' ),
'id' => 'searchform'
],
Html::hidden( 'title', $this->get( 'searchtitle' ) ) .
$this->makeSearchInput( [ 'id' => $searchInputId ] ) .
$this->makeSearchButton( 'go', [
'id' => 'searchGoButton',
'class' => 'searchButton',
'value' => $this->getMsg( 'searcharticle' )->text()
] ) .
$optionButtons
);
$html .= $this->getBox( 'search', $searchForm, null, [
'search-input-id' => $searchInputId,
'role' => 'search',
'body-id' => 'searchBody'
] );
return $html;
}
/**
* Generate the class for the globalWrapper element
* @return string
*/
private function getGlobalWrapperAttributes( Config $config, string $action ): string {
$siteWidth = $config->get( 'CavendishSiteWidth' );
$qrCodeMode = $config->get( 'CavendishQRCodeMode' );
$sidebarSearchbox = $config->get( 'CavendishSidebarSearchbox' );
$classes = [
htmlspecialchars( $action, ENT_QUOTES )
];
if ( $qrCodeMode === 'print' ) {
$classes[] = 'cavendish-qr-code-mode-print';
} elseif ( $qrCodeMode === 'all' ) {
$classes[] = 'cavendish-qr-code-mode-all';
}
if ( $sidebarSearchbox ) {
$classes[] = 'cavendish-sidebar-searchbox';
}
return Html::expandAttributes( [
'id' => 'globalWrapper',
'class' => $classes,
'style' => $siteWidth !== false ? 'width: ' . $sideWidth . 'px;' : null,
] );
}
/**
* Template filter callback for Cavendish skin.
* Takes an associative array of data set from a SkinTemplate-based
* class, and a wrapper for MediaWiki's localization database, and
* outputs a formatted page.
*/
function execute() {
$this->skin = $skin = $this->data['skin'];
$action = $skin->getRequest()->getText( 'action', 'view' );
$config = $skin->getConfig();
$this->data['pageLanguage'] =
$skin->getTitle()->getPageLanguage()->getHtmlCode();
// <body> starts here
?>
<div id="internal"></div>
<div <?php echo $this->getGlobalWrapperAttributes( $config, $action ) ?>>
<div id="p-personal" class="portlet">
<h5><?php $this->msg( 'personaltools' ) ?></h5>
<div class="pBody">
<ul<?php $this->html( 'userlangattributes' ) ?>>
<?php
$personalTools = $this->getPersonalTools();
foreach ( $personalTools as $key => $item ) {
?>
<li id="<?php echo Sanitizer::escapeIdForAttribute( "pt-$key" ) ?>" class="<?php
if ( $item['active'] ?? false ) { ?>active <?php } ?>top-nav-element">
<span class="top-nav-left">&nbsp;</span>
<?php
// Crappy hack for Echo
// Without this the Echo links will generate an E_NOTICE:
// Notice: Array to string conversion in ../includes/skins/BaseTemplate.php on line 410
if ( isset( $item['links'][0]['class'] ) && is_array( $item['links'][0]['class'] ) ) {
$classHTML = '';
foreach ( $item['links'][0]['class'] as $class ) {
$classHTML .= $class . ' ';
}
$item['links'][0]['class'] = $classHTML;
}
if ( $key !== 'anonuserpage' ) {
echo $this->makeLink( $key, $item['links'][0], [ 'link-class' => 'top-nav-mid' ] );
} else {
// Stupid hack for anonuserpage, which is the "Not logged in" text, which is indeed
// just a snippet of text, not a link; but we still need the class there to have the area
// display properly
echo '<p class="top-nav-mid">' . $this->makeLink( $key, $item['links'][0] ) . '</p>';
}
?>
<span class="top-nav-right">&nbsp;</span>
</li>
<?php
}
?>
</ul>
</div>
</div>
<div id="header">
<a name="top" id="contentTop"></a>
<div id="site-logo">
<?php
$logos = SkinModule::getAvailableLogos( $config );
$logoUrl = $logos['1x'] ?? null;
$logoAttributes = [
'href' => $this->data['nav_urls']['mainpage']['href'],
'title' => $this->getMsg( 'mainpage' ),
'id' => 'site-logo-link',
];
?>
<a <?php echo Html::expandAttributes( $logoAttributes ); ?>>
<?php if ( $logoUrl ): ?>
<img src="<?php echo htmlspecialchars( $logoUrl ) ?>" width="48" height="48" alt="">
<?php endif; ?>
<span id="site-name"><?php $this->text( 'pagetitle' ) ?></span>
</a></div>
<div id="p-cactions" class="portlet" role="navigation">
<ul>
<?php
foreach ( $this->data['content_actions'] as $key => $tab ) {
echo $this->makeListItem( $key, $tab );
}
?>
</ul>
</div>
<?php
// TODO Searchbox Handling
echo $this->getSearchBox();
?>
</div>
<div id="mBody">
<div id="side">
<div id="nav">
<?php echo $this->renderPortals( $this->data['sidebar'] ); ?>
</div>
</div>
</div><!-- end of #mBody div -->
<div id="column-content">
<div id="content" class="mw-body">
<a id="top"></a>
<?php if ( $this->data['sitenotice'] ) { ?><div id="siteNotice"><?php $this->html( 'sitenotice' ) ?></div><?php } ?>
<?php echo $this->getIndicators() ?>
<h1 id="firstHeading" class="firstHeading" lang="<?php $this->text( 'pageLanguage' ); ?>"><?php $this->html( 'title' ) ?></h1>
<div id="bodyContent" class="mw-body-content">
<h3 id="siteSub"><?php $this->msg( 'tagline' ) ?></h3>
<div id="contentSub"<?php $this->html( 'userlangattributes' ) ?>><?php $this->html( 'subtitle' ) ?></div>
<?php if ( $this->data['undelete'] ) { ?><div id="contentSub2"><?php $this->html( 'undelete' ) ?></div><?php } ?>
<?php if ( $this->data['newtalk'] ) { ?><div class="usermessage"><?php $this->html( 'newtalk' ) ?></div><?php } ?>
<div id="jump-to-nav" class="mw-jump"><?php $this->msg( 'jumpto' ) ?> <a href="#column-one"><?php $this->msg( 'jumptonavigation' ) ?></a>, <a href="#searchInput"><?php $this->msg( 'jumptosearch' ) ?></a></div>
<!-- start content -->
<?php
$this->html( 'bodytext' );
if ( $this->data['catlinks'] ) {
$this->html( 'catlinks' );
}
?>
<!-- end content -->
<?php
if ( $this->data['dataAfterContent'] ) {
$this->html( 'dataAfterContent' );
}
?>
</div>
</div><!-- end of #content div -->
</div>
</div><!-- end of #globalWrapper div -->
<div class="visualClear"></div>
<div id="footer" role="contentinfo"<?php $this->html( 'userlangattributes' ) ?>>
<table>
<tr>
<td rowspan="2" class="f-iconsection">
<?php
$icons = $this->get( 'footericons' );
$icon = $icons['copyright'][0] ?? null;
if ( $icon ) {
?><div id="f-copyrightico"><?php
echo $skin->makeFooterIcon( $icon );
?></div><?php
}
?>
</td>
<td align="center">
<?php // Generate additional footer links
$footerLinks = [
'lastmod', 'viewcount', 'credits', 'copyright',
'privacy', 'about', 'disclaimer', 'tagline',
];
$validFooterLinks = [];
foreach ( $footerLinks as $aLink ) {
if ( isset( $this->data[$aLink] ) && $this->data[$aLink] ) {
$validFooterLinks[] = $aLink;
}
}
if ( count( $validFooterLinks ) > 0 ) {
?> <ul id="f-list">
<?php
foreach ( $validFooterLinks as $aLink ) {
if ( isset( $this->data[$aLink] ) && $this->data[$aLink] ) {
?> <li id="f-<?php echo $aLink ?>"><?php $this->html( $aLink ) ?></li>
<?php }
}
}
?>
</ul>
</td>
<td rowspan="2" class="f-iconsection">
<?php
$validFooterIcons = $this->get( 'footericons' );
unset( $validFooterIcons['copyright'] );
foreach ( $validFooterIcons as $blockName => $footerIcons ) { ?>
<div id="f-<?php echo htmlspecialchars( $blockName ); ?>ico"><?php
foreach ( $footerIcons as $icon ) {
// Need to check emptiness before rendering to prevent core from throwing
// an E_NOTICE:
// PHP Notice: Undefined index: alt in <path to MW>/includes/skins/Skin.php on line 1007
if ( !empty( $icon ) ) {
echo $this->skin->makeFooterIcon( $icon );
}
}
?></div>
<?php
}
// Show a Quick Response (QR) code if enabled in configuration
if ( $this->config->get( 'CavendishQRCode' ) ) {
$QRURL = htmlentities( $skin->getTitle()->getFullURL() ) . $this->config->get( 'CavendishQRUrlAdd' );
?>
<div id="qrcode">
<a href="http://goqr.me/" style="border:0 none;cursor:default;text-decoration:none;">
<img src="http://api.qrserver.com/v1/create-qr-code/?data=<?php echo $QRURL; ?>&#38;size=160x160" height="80" width="80" alt="QR Code generator" title="" />
</a>
</div>
<?php } ?>
</td>
</tr>
<tr>
<td>
<div id="skin-info">
<?php echo $skin->msg( 'cavendish-skin-info', '3.0.0' )->parse() ?>
</div>
</td>
</tr>
</table>
</div><!-- end of the FOOTER div -->
<!-- scripts and debugging information -->
<?php
}
/**
* @todo FIXME: this is literal copypasta from MonoBookTemplate.php just
* so that we can comment out one line. That sucks.
* @param array $sidebar
*/
protected function renderPortals( $sidebar ) {
$html = '';
$languagesHTML = '';
if ( !isset( $sidebar['SEARCH'] ) ) {
$sidebar['SEARCH'] = true;
}
if ( !isset( $sidebar['TOOLBOX'] ) ) {
$sidebar['TOOLBOX'] = true;
}
if ( !isset( $sidebar['LANGUAGES'] ) ) {
$sidebar['LANGUAGES'] = true;
}
foreach ( $sidebar as $boxName => $content ) {
if ( $content === false ) {
continue;
}
// Numeric strings gets an integer when set as key, cast back - T73639
$boxName = (string)$boxName;
if ( $boxName == 'SEARCH' ) {
// ignore.
} elseif ( $boxName == 'TOOLBOX' ) {
$html .= $this->getToolboxBox( $content );
} elseif ( $boxName == 'LANGUAGES' ) {
$languagesHTML = $this->getLanguageBox( $content );
} else {
$html .= $this->getBox(
$boxName,
$content,
null,
[ 'extra-classes' => 'generated-sidebar' ]
);
}
}
// Output language portal last given it can be long
// on articles which support multiple languages (T254546)
return $html . $languagesHTML;
}
} // end of class

View File

@ -0,0 +1,20 @@
/*jshint node:true */
module.exports = function ( grunt ) {
grunt.loadNpmTasks( 'grunt-banana-checker' );
grunt.loadNpmTasks( 'grunt-jsonlint' );
grunt.initConfig( {
banana: {
all: 'i18n/'
},
jsonlint: {
all: [
'**/*.json',
'!node_modules/**'
]
}
} );
grunt.registerTask( 'test', [ 'jsonlint', 'banana' ] );
grunt.registerTask( 'default', 'test' );
};

View File

@ -0,0 +1,22 @@
# Cavendish
Cavendish MediaWiki skin version 2.6.0
## Compatibility
This version of the skin is compatible with MW 1.35+
## Customization
There are different options that can be set in `LocalSettings.php`.
`$wgLogos['1x-options'] = [ 'width' => 10, 'height' => 20, 'margin' => 10 ]`
* width of the logo as a number in px
* height of the logo as a number in px
* offset on top of the logo
`$wgCavendishSiteWidth`
fixed width of the content area, if not set it's dynamic (default is false, that means dynamic)
`$wgCavendishQRCode`
Whether to add QR code to all pages (true) or not (false)
`$wgCavendishQRUrlAdd`
Used to track campaign for entry through QR Code in analytics software (e.g. Google Analytics, Piwik, ...); default is "?pk_campaign=qr-code"

View File

@ -0,0 +1,16 @@
{
"require-dev": {
"mediawiki/minus-x": "1.1.3",
"php-parallel-lint/php-console-highlighter": "1.0.0",
"php-parallel-lint/php-parallel-lint": "1.4.0"
},
"scripts": {
"test": [
"parallel-lint . --exclude vendor --exclude node_modules",
"minus-x check ."
],
"fix": [
"minus-x fix ."
]
}
}

View File

@ -0,0 +1,13 @@
{
"@metadata": {
"authors": ["DaSch"]
},
"cavendish-prefs-talkpage": "[[mw:Skin talk:Cavendish|{{int:talk}}]]",
"cavendish.css": "/* CSS placed here will affect users of the Cavendish skin */",
"cavendish.js": "/* Any JavaScript here will be loaded for users using the Cavendish skin */",
"skinname-cavendish": "Cavendish (Blue)",
"skinname-cavendish-green": "Cavendish (Green)",
"skinname-cavendish-brown": "Cavendish (Brown)",
"cavendish-desc": "Mozilla Cavendish skin. Modified by DaSch for MW 1.19 and WeCoWi.",
"cavendish-skin-info": "Mozilla Cavendish skin<br />[https://phabricator.wikimedia.org/maniphest/task/edit/form/43/?tags=mediawiki-skins-Cavendish Report a bug] &ndash; Skin version: $1"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
/* Blue color */
#header {
background: #455372; /* Old browsers */
background: -moz-linear-gradient(top, #455372 0%, #455372 52%, #3f4c6b 67%, #2a3753 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#455372), color-stop(52%,#455372), color-stop(67%,#3f4c6b), color-stop(100%,#2a3753)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #455372 0%,#455372 52%,#3f4c6b 67%,#2a3753 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #455372 0%,#455372 52%,#3f4c6b 67%,#2a3753 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #455372 0%,#455372 52%,#3f4c6b 67%,#2a3753 100%); /* IE10+ */
background: linear-gradient(to bottom, #455372 0%,#455372 52%,#3f4c6b 67%,#2a3753 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#455372', endColorstr='#2a3753',GradientType=0 ); /* IE6-9 */
}
#header form label, #header label {
color: #ddd;
}
#header form #searchInput {
border: 1px solid #9097a2;
background-color: #d9dbe1;
}
/* p-cactions */
#p-cactions li {
border-bottom: 1px solid #515358;
background-color: #5F697e;
border-top-color: #5F697e;
}
#p-cactions li:hover {
border-top-color: #747f96;
background-color: #747f96;
}
#p-cactions li.selected a {
color: #455372;
}
/* NAV */
#nav {
background: #e0e9e9;
}
#nav div a:hover {
background: #e0e9e9;
}
#nav div a,
#nav div span.social {
background: #edf2f2;
border-bottom: 1px solid #ddd;
border-top: 1px solid #fff;
border-right: 1px solid #ddd;
}
/* preftoc */
#preftoc li:hover,
#preftoc li:focus,
#preftoc li.selected {
background-color: #e0e9e9;
}
/* fieldset */
fieldset {
border: 1px solid #2f6fab;
}
fieldset fieldset {
background-color: #e0e9e9;
}
/* mw-line */
.mw-line-odd {
background-color: #edf2f2;
}
.mw-line-even {
background-color: #e0e9e9;
}

View File

@ -0,0 +1,91 @@
/* Brown color */
#header {
background: #e3cead; /* Old browsers */
background: -moz-linear-gradient(top, #e3cead 0%, #dac39e 35%, #ccb186 75%, #c5aa7d 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#e3cead), color-stop(35%,#dac39e), color-stop(75%,#ccb186), color-stop(100%,#c5aa7d)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #e3cead 0%,#dac39e 35%,#ccb186 75%,#c5aa7d 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #e3cead 0%,#dac39e 35%,#ccb186 75%,#c5aa7d 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #e3cead 0%,#dac39e 35%,#ccb186 75%,#c5aa7d 100%); /* IE10+ */
background: linear-gradient(to bottom, #e3cead 0%,#dac39e 35%,#ccb186 75%,#c5aa7d 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e3cead', endColorstr='#c5aa7d',GradientType=0 ); /* IE6-9 */
}
#header form label,
#header label {
color: #774f03;
}
#header form #searchInput {
border: 1px solid #774f03;
background-color: #fbf4e8;
}
/* p-cactions */
#p-cactions li {
border-bottom: 1px solid #b48a48;
background-color: #885e1b;
border-top-color: #885e1b;
}
#p-cactions li:hover {
border-top-color: #b48a48;
background-color: #b48a48;
}
#p-cactions li.selected a {
color: #780000;
}
/* NAV */
#nav {
background: #e8ddca;
}
#nav div a:hover {
background: #e8ddca;
}
#nav div a,
#nav div span.social {
background-color:#fbf4e8;
border-bottom: 1px solid #ddd;
border-right: 1px solid #ddd;
border-top: 1px solid #fff;
}
/* preftoc */
#preftoc li:hover,
#preftoc li:focus,
#preftoc li.selected {
background-color: #e8ddca;
}
/* fieldset */
fieldset {
border: 1px solid #774F03;
}
fieldset fieldset {
background-color: #e8ddca;
}
/* mw-line */
.mw-line-odd {
background-color: #FBF4E8;
}
.mw-line-even {
background-color: #e8ddca;
}
/* different from blue.css */
.portlet li a:link {
color: #780000;
text-decoration: none;
}
.portlet li a:visited {
color: #774f03;
text-decoration: none;
}

View File

@ -0,0 +1,80 @@
/* Green color */
#header {
background: #379940; /* Old browsers */
background: -moz-linear-gradient(top, #379940 0%, #36983f 56%, #32903a 68%, #216d2b 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#379940), color-stop(56%,#36983f), color-stop(68%,#32903a), color-stop(100%,#216d2b)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #379940 0%,#36983f 56%,#32903a 68%,#216d2b 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #379940 0%,#36983f 56%,#32903a 68%,#216d2b 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #379940 0%,#36983f 56%,#32903a 68%,#216d2b 100%); /* IE10+ */
background: linear-gradient(to bottom, #379940 0%,#36983f 56%,#32903a 68%,#216d2b 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#379940', endColorstr='#216d2b',GradientType=0 ); /* IE6-9 */
}
#header form label,
#header label {
color: #89cc92;
}
#header form #searchInput {
border: 1px solid #23712d;
background-color: #89cc92;
}
/* p-cactions */
#p-cactions li {
border-bottom: 1px solid #56a05f;
background-color: #58ac64;
border-top-color: #58ac64;
}
#p-cactions li:hover {
border-top-color: #68cc76;
background-color: #68cc76;
}
#p-cactions li.selected a {
color: #455372;
}
/* NAV */
#nav {
background: #379940;
}
#nav div a:hover {
background: #379940;
}
#nav div a,
#nav div span.social {
background: #89cc92;
border-bottom: 1px solid #ddd;
border-top: 1px solid #fff;
border-right: 1px solid #ddd;
}
/* preftoc */
#preftoc li:hover,
#preftoc li:focus,
#preftoc li.selected {
background-color: #89cc92;
}
/* fieldset */
fieldset {
border: 1px solid #1fd931;
}
fieldset fieldset {
background-color: #89cc92;
}
/* mw-line */
.mw-line-odd {
background-color: #379940;
}
.mw-line-even {
background-color: #89cc92;
}

View File

@ -0,0 +1,6 @@
/** These styles are configurable via configuration */
.cavendish-qr-code-mode-all #f-poweredbyico,
.cavendish-qr-code-mode-print #qrcode,
.cavendish-sidebar-searchbox #nav #p-search {
display: none;
}

View File

@ -0,0 +1,134 @@
/**
* Stylesheet for Babel extension.
*
* This should be added to your MediaWiki:Common.css page when installing this
* extension and should not be modified. You may modify the CSS code on the
* MediaWiki:Common.css page to adjust colours etc.
*
* @addtogroup Extensions
*/
/* Babel wrapper layout. */
table.mw-babel-wrapper {
width: 238px;
float: right;
clear: right;
border-style: solid;
margin-right: 0;
border-width: 1px;
border-color: #99b3ff;
}
/* Babel box layout. */
div.mw-babel-box {
float: left;
clear: left;
margin: 1px;
}
div.mw-babel-box table {
width: 238px;
}
div.mw-babel-box table th {
width: 238px;
width: 45px;
height: 45px;
font-size: 14pt;
font-family: monospace;
}
div.mw-babel-box table td {
font-size: 8pt;
padding: 4pt;
line-height: 1.25em;
}
/* Babel box colours. */
div.mw-babel-box-0 {
border: solid #b7b7b7 1px;
}
div.mw-babel-box-1 {
border: solid #c0c8ff 1px;
}
div.mw-babel-box-2 {
border: solid #77e0e8 1px;
}
div.mw-babel-box-3 {
border: solid #99b3ff 1px;
}
div.mw-babel-box-4 {
border: solid #cc0 1px;
}
div.mw-babel-box-5 {
border: solid #f99c99 1px;
}
div.mw-babel-box-N {
border: solid #6ef7a7 1px;
}
div.mw-babel-box-0 table th {
background-color: #b7b7b7;
}
div.mw-babel-box-1 table th {
background-color: #c0c8ff;
}
div.mw-babel-box-2 table th {
background-color: #77e0e8;
}
div.mw-babel-box-3 table th {
background-color: #99b3ff;
}
div.mw-babel-box-4 table th {
background-color: #cc0;
}
div.mw-babel-box-5 table th {
background-color: #f99c99;
}
div.mw-babel-box-N table th{
background-color: #6ef7a7;
}
div.mw-babel-box-0 table {
background-color: #e8e8e8;
}
div.mw-babel-box-1 table {
background-color: #f0f8ff;
}
div.mw-babel-box-2 table {
background-color: #d0f8ff;
}
div.mw-babel-box-3 table {
background-color: #e0e8ff;
}
div.mw-babel-box-4 table {
background-color: #ff9;
}
div.mw-babel-box-5 table {
background-color: #f9cbc9;
}
div.mw-babel-box-N table {
background-color: #c5fcdc;
}
.babel-box td.babel-footer {
text-align: center;
}

View File

@ -0,0 +1,24 @@
ol.references {
font-size: 0.9em;
}
sup.reference {
font-size: 0.8em;
line-height: 0.8em;
vertical-align: top;
position: relative;
top: -0.2em;
white-space: nowrap;
}
.reference * {
vertical-align: top;
}
ol.references > li:target {
background-color: #def;
}
sup.reference:target {
background-color: #def;
}

View File

@ -0,0 +1,12 @@
/* Hide Echo's colorful little boxes indicating how many new alerts/notices
* the user has, because:
* 1) the data is already shown on the personal tools area, and thus
* 2) these visual indicators conflict with the above and look messy since
* Cavendish does not render the icons (should it? IMHO, yes. But I was
* unable to figure out how to get it to render the Echo icons, so this'll
* have to do for the time being.)
*/
li#pt-notifications-alert .mw-echo-notifications-badge::after,
li#pt-notifications-notice .mw-echo-notifications-badge::after {
display: none;
}

View File

@ -0,0 +1,10 @@
div.flaggedrevs_basic,
div.flaggedrevs_quality,
div.flaggedrevs_pristine,
div.flaggedrevs_notice {
border-radius: 6px;
}
table.flaggedrevs_editnotice {
margin-bottom: 1em;
}

View File

@ -0,0 +1,4 @@
.inputbox-element {
white-space: nowrap;
display: inline-block;
}

View File

@ -0,0 +1,25 @@
div.lqt-thread-toolbar {
right: 3em;
}
.lqt-thread-toolbar-command-list {
display: inline;
}
.lqt-talkpage-header {
border-radius: 6px;
border-width: 1px;
}
.lqt_header_content {
border-bottom-right-radius: 6px;
border-width: 1px;
}
a.lqt_watchlist_messages_notice {
border-radius: 6px;
background-color: #f2dede;
border-color: #eed3d7;
color: #b94a48;
padding: 0.5em 1.5em;
}

View File

@ -0,0 +1,17 @@
#p-personal .pBody li#pt-openidlogin {
background: none;
padding-left: 0;
}
.openid_large_link {
border-radius: 6px;
border-width: 1px;
}
.openid_small_providers_block {
padding-right: 5em;
}
#provider_form_openid input {
margin-top: 5px;
}

View File

@ -0,0 +1,37 @@
input.createboxInput {
margin-top: 1px;
margin-bottom: 1px;
}
/*
.mandatoryFieldSpan {
border: 1px solid #8B0000;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background: #FFA07A;
}
.inputSpan {
padding-top: 5px;
padding-bottom: 7px;
}
*/
.formtable td {
padding-top: 7px;
padding-bottom: 7px;
}
.formtable th {
padding-top: 7px;
padding-bottom: 7px;
padding-right: 4px;
}
#pfForm div.warningbox {
border: 1px solid #ff8c00 !important;
}
.checkboxLabel, .ui-widget {
white-space: nowrap;
}

View File

@ -0,0 +1,11 @@
.mw-highlight {
padding: 1em;
margin: 1em 0;
border: 1px dashed #2fab6f;
border-radius: 6px;
overflow-y: hidden;
}
.mw-highlight ol {
padding-left: 1em;
}

View File

@ -0,0 +1,4 @@
div div#drilldown-categories-wrapper {
padding-left: 0;
padding-right: 0;
}

View File

@ -0,0 +1,69 @@
table.smwtable tr.smwfooter td.sortbottom {
padding-top: 3px;
padding-bottom: 3px;
}
table.smwtable {
margin: 1em 1em 1em 0;
border: 1px #aaa solid;
border-collapse: collapse;
empty-cells: show;
border-radius: 6px;
}
table.smwtable th {
background-color: #e0e9e9;
border: 1px #aaa solid;
padding: 0.2em;
white-space: nowrap;
padding: 0.2em;
}
table.smwtable td {
border: 1px #aaa solid;
padding: 0.2em;
}
table.smwtable caption {
margin-left: inherit;
margin-right: inherit;
font-weight: bold;
}
table.smwtable tr.row-odd {
background-color: #f9f9f9;
}
table.smwtable tr.row-odd td {
border-top: none;
border-bottom: none;
}
table.smwtable tr.row-even {
background-color: #f1f1f1;
}
table.smwtable tr.row-even td {
border-top: none;
border-bottom: none;
}
.smwttactiveinline {
white-space: nowrap;
}
.smwfact {
border-radius: 6px;
box-shadow: 2px 2px 2px #808080;
}
#content div.smwfact table.smwfacttable {
border-radius: 0;
}
.smwtt {
font-size: 10pt;
}
#content table.smwb-factbox,
#content table.smwb-ifactbox {
border-radius: 0;
}

View File

@ -0,0 +1,107 @@
/*
* Styles for extensions
*/
/*
* Poem Extension
*/
.poem {
margin-left: 1.5em;
white-space: nowrap;
overflow: auto;
}
.poem p {
padding-left: 0.5em;
border-left: 1px solid #ffd700;
}
/* HeaderTabs */
#headertabs {
border: 1px solid #aaa;
}
/* Google Maps */
.pmap {
margin-top: -5px;
margin-bottom: 5px;
}
.google-map {
margin-bottom: 1.5em;
width: auto;
}
/* Semantic Result Formats */
/**
* As of 1 November 2020 there doesn't appear to be a ResourceLoader module we could piggyback on
* to load these styles when needed.
* @see https://github.com/SemanticMediaWiki/SemanticResultFormats/blob/master/formats/calendar/SRF_Calendar.php#L330
*/
#bodyContent table.navigation_table tr td.month_name {
font-size: 18pt;
}
#bodyContent table.month_calendar tr.weekdays {
background: #e0e9e9;
}
#bodyContent table.month_calendar td.today div.day {
background: #e0e9e9;
}
#bodyContent table.month_calendar td.weekend_day div.day {
color: red;
}
#bodyContent .srf-datatables select {
padding: 4px;
}
/* 4sq */
table .tips-4sq blockquote {
margin-right: 20px;
margin-left: 20px;
overflow-wrap: break-word;
word-wrap: break-word;
}
/* yelp */
table .yelp-reviews blockquote {
margin-right: 20px;
margin-left: 20px;
}
/* Social Sidebar WeCoWi */
#nav div li span.social {
padding-bottom: 6px;
padding-top: 6px;
}
#nav div li#buffer span.social {
padding-left: 19px;
}
#nav div li#facebook span span {
margin-left: -10px;
margin-top: -5px;
}
#nav div li#facebook span.social {
height: 19px;
}
#nav div li#twitter span.social iframe {
margin-left: 9px;
}
#nav a.twitter-share-button {
font-weight: normal;
border: none;
padding: 0 0 0 10px;
}
#nav li#google span.social,
#nav li#flattr span.social {
padding-left: 20px;
}

View File

@ -0,0 +1,12 @@
div.wikiEditor-ui div.section {
padding-left: 0;
}
div.wikiEditor-ui div.wikiEditor-ui-top {
font-size: 8.5pt;
}
div.wikiEditor-ui div.wikiEditor-ui-tabs div {
font-size: 9pt;
padding-bottom: 1px;
}

View File

@ -0,0 +1,29 @@
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@600;700&family=Quicksand:wght@400;500&display=swap');
#header #site-logo-link {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none;
}
#header #site-logo-link:hover,
#header #site-logo-link:focus {
text-decoration: none;
}
#header #site-logo-link img {
width: 48px;
height: 48px;
display: block;
flex-shrink: 0;
}
#site-name {
font-family: 'Comfortaa', sans-serif;
font-weight: 700;
font-size: 1.25rem;
color: #ffffff;
letter-spacing: -0.01em;
line-height: 1.2;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,10 @@
#editpage-copywarn {
margin-top: 2em;
margin-bottom: 2em;
border-radius: 6px;
padding: 0.5em;
}
input#wpSummary {
width: 80%;
}

View File

@ -0,0 +1,36 @@
/* the auto-generated edit comments */
.autocomment {
color: #808080;
}
#pagehistory span.user {
margin-left: 1.4em;
margin-right: 0.4em;
}
#pagehistory span.minor {
font-weight: bold;
}
#pagehistory li {
border: 1px solid #fff;
padding: 2px;
border-radius: 6px;
}
#pagehistory li:focus,
#pagehistory li:hover {
box-shadow: 0 0 2px inset;
}
#pagehistory li.selected {
background-color: #f9f9f9;
border: 1px dashed #aaa;
border-radius: 6px;
position: relative;
}
span.updatedmarker {
/* color: black; */
color: #00ff00;
}

View File

@ -0,0 +1,28 @@
ul#filetoc {
text-align: center;
border: 1px solid #aaa;
background-color: #f9f9f9;
padding: 5px;
font-size: 95%;
margin-bottom: 0.5em;
margin-left: 0;
margin-right: 0;
border-radius: 6px;
}
#filetoc li {
display: inline;
list-style-type: none;
padding-right: 2em;
}
table.filehistory {
overflow: scroll;
width: 100%;
font-size: 0.9em;
}
table.mw_metadata {
margin-left: 0;
margin-top: 1em;
}

View File

@ -0,0 +1,4 @@
.redirectText {
font-size: 150%;
margin: 5px;
}

View File

@ -0,0 +1,31 @@
table.diff {
background: #fff;
}
td.diff-otitle {
background: #fff;
}
td.diff-ntitle {
background: #fff;
}
td.diff-addedline {
background: #cfc;
font-size: smaller;
}
td.diff-deletedline {
background: #ffa;
font-size: smaller;
}
td.diff-context {
background: #eee;
font-size: smaller;
}
span.diffchange {
color: #f00;
font-weight: bold;
}

View File

@ -0,0 +1,33 @@
table.gallery {
border: 1px solid #ccc;
margin: 2px;
padding: 2px;
background-color: #fff;
}
table.gallery tr {
vertical-align: top;
}
table.gallery td {
vertical-align: top;
background-color: #f9f9f9;
border: solid 2px #fff;
}
div.gallerybox {
margin: 2px;
width: 150px;
}
div.gallerybox div.thumb {
text-align: center;
border: 1px solid #ccc;
margin: 2px;
}
div.gallerytext {
font-size: 94%;
padding: 2px 4px;
text-align: left;
}

View File

@ -0,0 +1,19 @@
#content table.mw-enhanced-rc {
margin-bottom: 0.1em;
}
td.mw-enhanced-rc {
font-family: Verdana, "DejaVu Sans", sans-serif;
font-weight: normal;
}
table.mw-enhanced-rc th,
table.mw-enhanced-rc td {
vertical-align: middle;
}
td.mw-enhanced-rc .newpage,
td.mw-enhanced-rc .minoredit,
td.mw-enhanced-rc .botedit {
font-weight: normal;
}

View File

@ -0,0 +1,37 @@
table#sv-ext,
table#sv-hooks,
table#sv-software,
table#mw-version-entrypoints-table,
.mw-special-Version #mw-content-text table {
padding: 0;
}
#sv-ext td,
#sv-hooks td,
#sv-software td,
#sv-ext th,
#sv-hooks th,
#sv-software th,
.mw-special-Version #bodyContent td,
.mw-special-Version #bodyContent th {
border: 1px solid #a0a0a0;
padding: 0.1em 0.2em 0.1em 0.2em;
}
#sv-ext th,
#sv-hooks th,
#sv-software th,
.mw-special-Version #bodyContent th {
background-color: #f0f0f0;
color: #000;
padding: 0.1em 0.2em 0.1em 0.2em;
}
tr.sv-space {
height: 0.8em;
border: none;
}
tr.sv-space td {
display: none;
}

View File

@ -0,0 +1,56 @@
/* Table of Contents */
#toc,
.toc,
.mw-warning {
border: 1px solid #aaa;
background-color: #f9f9f9;
padding: 5px;
font-size: 9pt;
border-radius: 6px;
box-shadow: 1px 1px 1px grey;
}
#toc,
.toc {
display: inline-block;
}
#toctitle h2 {
display: inline;
border: none;
padding: 0;
font-size: 9.5pt;
font-weight: bold;
}
#toc #toctitle,
.toc #toctitle,
#toc .toctitle,
.toc .toctitle {
text-align: center;
}
#toc ul,
.toc ul {
list-style-type: none;
list-style-image: none;
margin-left: 0;
padding-left: 0;
text-align: left;
}
#toc ul ul,
.toc ul ul {
margin: 0 0 0 2em;
}
#toctitle .toctoggle {
font-size: 8pt;
}
.toccolours {
background-color: #f9f9f9;
border: 1px solid #aaa;
font-size: 95%;
padding: 5px;
}

View File

@ -0,0 +1,76 @@
/* Stylesheet for printing */
body {
margin-left: 5em;
margin-right: 5em;
font-size: 11pt;
}
#p-personal,
#side,
#header,
#skin-info,
#f-numberofwatchingusers,
#f-viewcount,
#f-privacy,
#f-about,
#f-disclaimer {
display: none;
}
.f-iconsection #f-copyrightico {
display: block;
}
#qrcode {
display: block;
margin-left: 10px;
text-align:right;
}
#footer #f-list li {
white-space: normal;
}
#footer li {
list-style-type: none;
}
#footer table {
width: 100%;
}
#catlinks {
border-top: 1px solid #aaa;
margin-top: 1em;
padding-top: 1em;
}
#catlinks ul li {
font-style: italic;
}
table.float-right,
table.floatright,
div.float-right,
div.floatright {
clear: right;
float: right;
margin-left: 0.7em;
margin-right: 0;
position: relative;
}
table.float-left,
table.floatleft,
div.float-left,
div.floatleft {
clear: left;
float: left;
margin-right: 0.7em;
margin-left: 0;
position: relative;
}
table p {
margin: 0;
}

View File

@ -0,0 +1,213 @@
{
"name": "Cavendish",
"author": [
"Daniel Schuba",
"Jack Phoenix",
"Samantha Nguyen",
"Jon Robson"
],
"version": "3.0.0",
"@note": "Remember to update the version number in CavendishTemplate.php, too, since it's passed to the \"cavendish-skin-info\" i18n message!",
"url": "https://www.mediawiki.org/wiki/Skin:Cavendish",
"descriptionmsg": "cavendish-desc",
"namemsg": "skinname-cavendish",
"license-name": "GPL-2.0-or-later",
"type": "skin",
"requires": {
"MediaWiki": ">= 1.41.0"
},
"ConfigRegistry": {
"cavendish": "MediaWiki\\Config\\GlobalVarConfig::newInstance"
},
"config": {
"CavendishQRUrlAdd": "?pk_campaign=qr-code",
"CavendishQRCode": false,
"CavendishQRCodeMode": "all",
"CavendishSiteWidth": false,
"CavendishSidebarSearchbox": false
},
"ValidSkinNames": {
"cavendish": {
"class": "SkinTemplate",
"args": [
{
"name": "cavendish",
"bodyOnly": true,
"template": "CavendishTemplate",
"styles": [
"skins.cavendish",
"skins.cavendish.blue"
]
}
]
},
"cavendish-brown": {
"class": "SkinTemplate",
"args": [
{
"name": "cavendish-brown",
"template": "CavendishTemplate",
"styles": [
"skins.cavendish",
"skins.cavendish.brown"
]
}
]
},
"cavendish-green": {
"class": "SkinTemplate",
"args": [
{
"name": "cavendish-green",
"template": "CavendishTemplate",
"styles": [
"skins.cavendish",
"skins.cavendish.green"
]
}
]
}
},
"MessagesDirs": {
"Cavendish": [
"i18n"
]
},
"ResourceModules": {
"skins.cavendish": {
"class": "MediaWiki\\ResourceLoader\\SkinModule",
"features": [
"elements",
"interface",
"logo",
"content-links",
"content-media",
"interface-message-box",
"interface-category",
"content-tables",
"i18n-ordered-lists",
"i18n-all-lists-margins",
"i18n-headings"
],
"styles": {
"resources/cavendish.css": {
"media": "screen"
},
"resources/header.css": {
"media": "screen"
},
"resources/print.css": {
"media": "print"
},
"resources/dynamic.css": true
}
},
"skins.cavendish.extensions": {
"styles": {
"resources/extensions/extensions.css": {
"media": "screen"
}
}
},
"skins.cavendish.blue": {
"styles": {
"resources/colors/blue.css": {
"media": "screen"
}
}
},
"skins.cavendish.brown": {
"styles": {
"resources/colors/brown.css": {
"media": "screen"
}
}
},
"skins.cavendish.green": {
"styles": {
"resources/colors/green.css": {
"media": "screen"
}
}
}
},
"ResourceFileModulePaths": {
"localBasePath": "",
"remoteSkinPath": "Cavendish"
},
"AutoloadClasses": {
"CavendishTemplate": "CavendishTemplate.php"
},
"ResourceModuleSkinStyles": {
"cavendish-brown": {
"+ext.babel": "resources/extensions/babel.css",
"+ext.cite.style": "resources/extensions/ext.cite.style.css",
"+ext.echo.styles.badge": "resources/extensions/ext.echo.styles.badge.css",
"+ext.flaggedRevs.basic": "resources/extensions/ext.flaggedRevs.basic.css",
"+ext.inputBox.styles": "resources/extensions/ext.inputBox.styles.css",
"+ext.liquidThreads": "resources/extensions/ext.liquidThreads.css",
"+ext.openid.plain": "resources/extensions/ext.openid.plain.css",
"+ext.pageforms.main": "resources/extensions/ext.pageforms.main.css",
"+ext.pygments": "resources/extensions/ext.pygments.css",
"+ext.semanticdrilldown.main": "resources/extensions/ext.semanticdrilldown.main.css",
"+ext.smw.style": "resources/extensions/ext.smw.style.css",
"+jquery.wikiEditor": "resources/extensions/jquery.wikiEditor.css",
"+mediawiki.action.edit": "resources/mediawiki/action.edit.css",
"+mediawiki.action.history.styles": "resources/mediawiki/action.history.styles.css",
"+mediawiki.action.view.filepage": "resources/mediawiki/action.view.filepage.css",
"+mediawiki.action.view.redirectPage": "resources/mediawiki/action.view.redirectPage.css",
"+mediawiki.diff.styles": "resources/mediawiki/diff.styles.css",
"+mediawiki.page.gallery.styles": "resources/mediawiki/page.gallery.styles.css",
"+mediawiki.special.changeslist.enhanced": "resources/mediawiki/special.changeslist.enhanced.css",
"+mediawiki.special.version": "resources/mediawiki/special.version.css",
"+mediawiki.toc": "resources/mediawiki/toc.css"
},
"cavendish-green": {
"+ext.babel": "resources/extensions/babel.css",
"+ext.cite.style": "resources/extensions/ext.cite.style.css",
"+ext.echo.styles.badge": "resources/extensions/ext.echo.styles.badge.css",
"+ext.flaggedRevs.basic": "resources/extensions/ext.flaggedRevs.basic.css",
"+ext.inputBox.styles": "resources/extensions/ext.inputBox.styles.css",
"+ext.liquidThreads": "resources/extensions/ext.liquidThreads.css",
"+ext.openid.plain": "resources/extensions/ext.openid.plain.css",
"+ext.pageforms.main": "resources/extensions/ext.pageforms.main.css",
"+ext.pygments": "resources/extensions/ext.pygments.css",
"+ext.semanticdrilldown.main": "resources/extensions/ext.semanticdrilldown.main.css",
"+ext.smw.style": "resources/extensions/ext.smw.style.css",
"+jquery.wikiEditor": "resources/extensions/jquery.wikiEditor.css",
"+mediawiki.action.edit": "resources/mediawiki/action.edit.css",
"+mediawiki.action.history.styles": "resources/mediawiki/action.history.styles.css",
"+mediawiki.action.view.filepage": "resources/mediawiki/action.view.filepage.css",
"+mediawiki.action.view.redirectPage": "resources/mediawiki/action.view.redirectPage.css",
"+mediawiki.diff.styles": "resources/mediawiki/diff.styles.css",
"+mediawiki.page.gallery.styles": "resources/mediawiki/page.gallery.styles.css",
"+mediawiki.special.changeslist.enhanced": "resources/mediawiki/special.changeslist.enhanced.css",
"+mediawiki.special.version": "resources/mediawiki/special.version.css",
"+mediawiki.toc": "resources/mediawiki/toc.css"
},
"cavendish": {
"+ext.babel": "resources/extensions/babel.css",
"+ext.cite.style": "resources/extensions/ext.cite.style.css",
"+ext.echo.styles.badge": "resources/extensions/ext.echo.styles.badge.css",
"+ext.flaggedRevs.basic": "resources/extensions/ext.flaggedRevs.basic.css",
"+ext.inputBox.styles": "resources/extensions/ext.inputBox.styles.css",
"+ext.liquidThreads": "resources/extensions/ext.liquidThreads.css",
"+ext.openid.plain": "resources/extensions/ext.openid.plain.css",
"+ext.pageforms.main": "resources/extensions/ext.pageforms.main.css",
"+ext.pygments": "resources/extensions/ext.pygments.css",
"+ext.semanticdrilldown.main": "resources/extensions/ext.semanticdrilldown.main.css",
"+ext.smw.style": "resources/extensions/ext.smw.style.css",
"+jquery.wikiEditor": "resources/extensions/jquery.wikiEditor.css",
"+mediawiki.action.edit": "resources/mediawiki/action.edit.css",
"+mediawiki.action.history.styles": "resources/mediawiki/action.history.styles.css",
"+mediawiki.action.view.filepage": "resources/mediawiki/action.view.filepage.css",
"+mediawiki.action.view.redirectPage": "resources/mediawiki/action.view.redirectPage.css",
"+mediawiki.diff.styles": "resources/mediawiki/diff.styles.css",
"+mediawiki.page.gallery.styles": "resources/mediawiki/page.gallery.styles.css",
"+mediawiki.special.changeslist.enhanced": "resources/mediawiki/special.changeslist.enhanced.css",
"+mediawiki.special.version": "resources/mediawiki/special.version.css",
"+mediawiki.toc": "resources/mediawiki/toc.css"
}
},
"manifest_version": 1
}

View File

@ -10,7 +10,10 @@
"dev": "pwsh -ExecutionPolicy Bypass -File ./scripts/dev.ps1",
"a11y:pa11y": "pa11y-ci --config ./tests/accessibility/pa11y.config.json",
"a11y:lighthouse": "lhci autorun --config=./tests/accessibility/lighthouserc.json",
"a11y:all": "npm run a11y:pa11y && npm run a11y:lighthouse"
"a11y:all": "npm run a11y:pa11y && npm run a11y:lighthouse",
"deploy": "pwsh -ExecutionPolicy Bypass -File ./scripts/deploy.ps1 -Mode skin",
"deploy:all": "pwsh -ExecutionPolicy Bypass -File ./scripts/deploy.ps1 -Mode all",
"deploy:initial": "pwsh -ExecutionPolicy Bypass -File ./scripts/deploy.ps1 -Mode initial"
},
"devDependencies": {
"@lhci/cli": "^0.14.0",

153
scripts/deploy.ps1 Normal file
View File

@ -0,0 +1,153 @@
<#
.SYNOPSIS
Deployt GPLX MediaWiki auf den Bitpalast-Server.
.PARAMETER Mode
skin - Cavendish-Skin synchronisieren (Standard, schnell)
templates - Wiki-Seiten-Vorlagen synchronisieren
all - Skin + Templates
initial - Erstinstallation: gesamten MediaWiki-Core übertragen
.EXAMPLE
.\deploy.ps1 # Skin deployen
.\deploy.ps1 -Mode all # Skin + Templates
.\deploy.ps1 -Mode initial
#>
param(
[ValidateSet('skin', 'templates', 'all', 'initial')]
[string]$Mode = 'skin'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# === Config laden ===
$envFile = Join-Path $PSScriptRoot '..' '.deploy.env'
if (-not (Test-Path $envFile)) {
Write-Error @"
.deploy.env nicht gefunden.
Kopiere .deploy.example.env zu .deploy.env und trage deine Zugangsdaten ein.
"@
exit 1
}
Get-Content $envFile | ForEach-Object {
if ($_ -match '^\s*([^#][^=]+)=(.*)$') {
[System.Environment]::SetEnvironmentVariable($Matches[1].Trim(), $Matches[2].Trim(), 'Process')
}
}
$deployHost = $env:DEPLOY_HOST
$deployUser = $env:DEPLOY_USER
$mwPath = $env:DEPLOY_MW_PATH
$projectPath = $env:DEPLOY_PROJECT_PATH
$target = "${deployUser}@${deployHost}"
foreach ($var in @('DEPLOY_HOST','DEPLOY_USER','DEPLOY_MW_PATH')) {
if (-not [System.Environment]::GetEnvironmentVariable($var, 'Process')) {
Write-Error "$var ist nicht in .deploy.env gesetzt."
exit 1
}
}
# === WSL rsync oder scp ===
$hasWsl = $null -ne (Get-Command 'wsl' -ErrorAction SilentlyContinue)
function ConvertTo-WslPath([string]$winPath) {
$abs = [System.IO.Path]::GetFullPath($winPath)
$drive = $abs.Substring(0, 1).ToLower()
$rest = $abs.Substring(2).Replace('\', '/')
return "/mnt/$drive$rest"
}
function Sync-Dir([string]$localDir, [string]$remoteDir, [bool]$delete = $false) {
Write-Host " $localDir" -ForegroundColor DarkGray
Write-Host " -> ${target}:${remoteDir}" -ForegroundColor DarkGray
if ($hasWsl) {
$wslSrc = (ConvertTo-WslPath $localDir) + '/'
$rsyncArgs = @('-az', '--progress')
if ($delete) { $rsyncArgs += '--delete' }
$rsyncArgs += $wslSrc
$rsyncArgs += "${target}:${remoteDir}/"
wsl rsync @rsyncArgs
} else {
# Fallback: scp (kein automatisches Loeschen von Remotedateien)
Write-Warning "WSL nicht gefunden nutze scp. Geloeschte lokale Dateien bleiben auf dem Server."
ssh $target "mkdir -p '$remoteDir'"
scp -r "${localDir}\." "${target}:${remoteDir}/"
}
if ($LASTEXITCODE -ne 0) {
Write-Error "Uebertragung fehlgeschlagen (Exit $LASTEXITCODE)."
exit $LASTEXITCODE
}
}
$repoRoot = Split-Path $PSScriptRoot
# === Deploy-Modi ===
switch ($Mode) {
'skin' {
Write-Host "Deploye Cavendish-Skin..." -ForegroundColor Cyan
Sync-Dir "$repoRoot\mediawiki\skins\Cavendish" "$mwPath/skins/Cavendish" -delete $true
Write-Host "Fertig." -ForegroundColor Green
}
'templates' {
if (-not $projectPath) {
Write-Error "DEPLOY_PROJECT_PATH muss in .deploy.env gesetzt sein."
exit 1
}
Write-Host "Deploye Wiki-Templates..." -ForegroundColor Cyan
Sync-Dir "$repoRoot\scripts\templates" "$projectPath/scripts/templates"
Write-Host "Fertig." -ForegroundColor Green
}
'all' {
Write-Host "Deploye Skin + Templates..." -ForegroundColor Cyan
Sync-Dir "$repoRoot\mediawiki\skins\Cavendish" "$mwPath/skins/Cavendish" -delete $true
if ($projectPath) {
Sync-Dir "$repoRoot\scripts\templates" "$projectPath/scripts/templates"
}
Write-Host "Fertig." -ForegroundColor Green
}
'initial' {
Write-Host "Erstinstallation uebertraege MediaWiki-Core..." -ForegroundColor Yellow
Write-Host "Das kann einige Minuten dauern." -ForegroundColor DarkGray
$excludes = @(
'--exclude=.git',
'--exclude=vendor/',
'--exclude=images/',
'--exclude=cache/',
'--exclude=LocalSettings.php'
)
if ($hasWsl) {
$wslSrc = (ConvertTo-WslPath "$repoRoot\mediawiki") + '/'
$rsyncArgs = @('-az', '--progress') + $excludes + @($wslSrc, "${target}:${mwPath}/")
wsl rsync @rsyncArgs
} else {
Write-Warning "Fuer die Erstinstallation wird WSL empfohlen (grosse Datenmenge)."
Write-Warning "Alternative: WinSCP GUI verwenden oder WSL installieren."
exit 1
}
Write-Host ""
Write-Host "Erstinstallation abgeschlossen." -ForegroundColor Green
Write-Host ""
Write-Host "Naechste Schritte auf dem Server:" -ForegroundColor Yellow
Write-Host " 1. LocalSettings.php anlegen:"
Write-Host " Vorlage: mediawiki/LocalSettings.production.example.php"
Write-Host " 2. Composer-Abhaengigkeiten installieren:"
Write-Host " cd $mwPath && composer install --no-dev"
Write-Host " 3. Datenbank einrichten:"
Write-Host " php maintenance/run.php install ..."
Write-Host " oder: Dump importieren mit mysql -u user -p dbname < dump.sql"
Write-Host " 4. SMW initialisieren:"
Write-Host " php maintenance/run.php setupStore"
}
}

View File

@ -0,0 +1,11 @@
Pages in this category cover clinical evaluation, clinical performance, post-market clinical follow-up (PMCF), and post-market performance follow-up (PMPF).
== Pages in this category ==
{{#ask: [[Category:Clinical]]
| ?Document name = Name
| ?Scope = Scope
| format=table
| headers=show
| limit=50
}}

View File

@ -0,0 +1,22 @@
Pages in this category cover regulatory documents and references in the In Vitro Diagnostics (IVD) scope.
== Documents ==
{{#ask: [[Category:In Vitro Diagnostics]] [[Status::+]]
| ?Document name = Name
| ?Status = Status
| ?Area of validity = Region
| ?Version = Version
| format=table
| headers=show
| limit=50
| sort=Status
}}
== Browse by topic ==
* [[:Category:Clinical|Clinical]]
* [[:Category:Post-Market Surveillance|Post-Market Surveillance]]
* [[:Category:Labelling|Labelling]]
* [[:Category:Traceability|Traceability]]
* [[:Category:Performance Evaluation|Performance Evaluation]]

View File

@ -0,0 +1,11 @@
Pages in this category cover labelling requirements, UDI carriers, and packaging obligations.
== Pages in this category ==
{{#ask: [[Category:Labelling]]
| ?Document name = Name
| ?Scope = Scope
| format=table
| headers=show
| limit=50
}}

View File

@ -0,0 +1,21 @@
Pages in this category cover regulatory documents and references in the Medical Devices (MD) scope.
== Documents ==
{{#ask: [[Category:Medical Devices]] [[Status::+]]
| ?Document name = Name
| ?Status = Status
| ?Area of validity = Region
| ?Version = Version
| format=table
| headers=show
| limit=50
| sort=Status
}}
== Browse by topic ==
* [[:Category:Clinical|Clinical]]
* [[:Category:Post-Market Surveillance|Post-Market Surveillance]]
* [[:Category:Labelling|Labelling]]
* [[:Category:Traceability|Traceability]]

View File

@ -0,0 +1,11 @@
Pages in this category cover post-market surveillance (PMS), post-market clinical follow-up (PMCF), post-market performance follow-up (PMPF), and periodic safety update reports (PSUR).
== Pages in this category ==
{{#ask: [[Category:Post-Market Surveillance]]
| ?Document name = Name
| ?Scope = Scope
| format=table
| headers=show
| limit=50
}}

View File

@ -0,0 +1,70 @@
This page lists all regulatory documents in this wiki. Use the sections below to browse by status or scope.
== Current documents ==
{{#ask: [[Status::Current]]
| ?Document name = Name
| ?Scope = Scope
| ?Area of validity = Region
| ?Document type = Type
| format=table
| headers=show
| limit=100
| sort=Scope
}}
== Grouped by scope ==
=== Medical Devices (MD) ===
{{#ask: [[Scope::~*MD*]]
| ?Document name = Name
| ?Status = Status
| ?Area of validity = Region
| format=table
| headers=show
| limit=50
}}
=== In Vitro Diagnostics (IVD) ===
{{#ask: [[Scope::~*IVD*]]
| ?Document name = Name
| ?Status = Status
| ?Area of validity = Region
| format=table
| headers=show
| limit=50
}}
=== GMP / PV / NIS ===
{{#ask: [[Scope::~*GMP*]] OR [[Scope::~*PV*]] OR [[Scope::~*NIS*]]
| ?Document name = Name
| ?Scope = Scope
| ?Status = Status
| format=table
| headers=show
| limit=50
}}
== Draft and not yet promulgated ==
{{#ask: [[Status::Draft]] OR [[Status::Final not promulgated]]
| ?Document name = Name
| ?Scope = Scope
| ?Area of validity = Region
| format=table
| headers=show
| limit=50
}}
== Superseded ==
{{#ask: [[Status::Superseded]]
| ?Document name = Name
| ?Scope = Scope
| format=table
| headers=show
| limit=50
}}

View File

@ -0,0 +1,45 @@
{{RegDocument
| area_of_validity = EU
| scope = IVD
| document_type = Regulation
| name = In Vitro Diagnostic Medical Devices Regulation (EU) 2017/746
| version = 2017/746, amended by 2022/1107
| source = https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32017R0746
| language = EN, DE, FR (all EU official languages)
| status = Current
| restricted_access = No
}}
The In Vitro Diagnostic Medical Devices Regulation (IVDR) 2017/746 replaces the former IVD Directive (IVDD 98/79/EC). It introduces a significantly stricter framework for in vitro diagnostic medical devices, including reclassification of the majority of IVDs into higher risk classes requiring Notified Body involvement.
Full application started on 26 May 2022. The amendment 2022/1107 introduced a phased transition for legacy devices based on device class and Notified Body capacity.
== Key references ==
{| class="wikitable" style="width:100%;"
! Topic / Keyword(s) !! Category / Chapter !! Scope !! Linked document !! Description
{{Reference
| topic = Post-market performance follow-up, PMPF
| category = Clinical
| scope = IVD
| linked_document = [[IVDR (EU) 2017/746 Articles 7881 & Annex XIII Part B: Post-Market Performance Follow-Up]]
| description = Articles 7881 and Annex XIII Part B define the mandatory post-market performance follow-up (PMPF) system, equivalent to PMCF under MDR.
}}
{{Reference
| topic = Unique Device Identification, UDI, Traceability
| category = Labelling
| scope = IVD
| linked_document = [[IVDR (EU) 2017/746 Articles 2426 & Annex VI: UDI]]
| description = Articles 2426 and Annex VI establish the UDI system for IVDs, aligned with the MDR UDI framework.
}}
{{Reference
| topic = Performance evaluation, Scientific validity
| category = Clinical
| scope = IVD
| linked_document = [[IVDR (EU) 2017/746 Article 56 & Annex XIII Part A: Performance Evaluation]]
| description = Article 56 and Annex XIII Part A define performance evaluation requirements, including analytical performance, clinical performance, and scientific validity.
}}
|}
[[Category:In Vitro Diagnostics]]
[[Category:EU Regulation]]

View File

@ -0,0 +1,44 @@
This wiki is a structured reference database for regulatory documents in the medical devices, in vitro diagnostics, and pharmaceutical sectors. Each document is stored with standardized metadata (scope, validity status, region, official source) and linked to individual pages for its key articles, annexes, and provisions, organized by topic.
The database currently covers regulations, directives, and guidelines in the MD, IVD, GMP, PV, and NIS scopes. Cross-references between equivalent provisions in different regulations are maintained explicitly, for example between MDR and IVDR requirements on post-market surveillance or UDI.
Structured queries are available on the [[Document_Index|Document Index]] and on each category page. Use [[Special:Ask|Special:Ask]] for custom queries across any combination of scope, status, region, or topic.
All registered users can contribute. New pages require reviewer approval before becoming publicly visible.
== Quick navigation ==
{| style="width:100%; border-collapse:collapse;"
|-
| style="width:33%; vertical-align:top; padding:0 1em 1em 0;" |
=== By document ===
* [[Document_Index|All documents]]
* [[Special:RecentChanges|Recent changes]]
* [[Special:Ask|Custom query]]
| style="width:33%; vertical-align:top; padding:0 1em 1em 0;" |
=== By scope ===
* [[:Category:Medical Devices|Medical Devices (MD)]]
* [[:Category:In Vitro Diagnostics|In Vitro Diagnostics (IVD)]]
| style="width:33%; vertical-align:top; padding:0 1em 1em 0;" |
=== By topic ===
* [[:Category:Clinical|Clinical]]
* [[:Category:Post-Market Surveillance|Post-Market Surveillance]]
* [[:Category:Labelling|Labelling]]
* [[:Category:Traceability|Traceability]]
|}
== Current documents ==
{{#ask: [[Status::Current]]
| ?Document name = Name
| ?Scope = Scope
| ?Area of validity = Region
| format=table
| headers=show
| limit=10
| sort=Scope
}}
[[Document_Index|View all documents →]]

View File

@ -0,0 +1 @@
PROPERTIES_BATCH

View File

@ -0,0 +1,34 @@
{{RegDocument
| area_of_validity = EU
| scope = IVD
| document_type = Regulation Article Reference
| name = IVDR (EU) 2017/746 Articles 7881 & Annex XIII Part B: Post-Market Performance Follow-Up
| version = 2017/746
| source = https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32017R0746#d1e6179-1-1
| language = EN, DE, FR
| status = Current
| restricted_access = No
}}
Articles 78 to 81 and Annex XIII Part B of the IVDR establish the mandatory post-market performance follow-up (PMPF) system for IVD manufacturers. PMPF is the IVD equivalent of PMCF under the MDR.
== Summary ==
Manufacturers must proactively collect and evaluate data from devices already on the market to confirm safety, performance, and scientific validity throughout the device lifetime.
Key obligations:
* Article 78 General post-market surveillance requirements
* Article 79 Post-market surveillance plan
* Article 80 Post-market surveillance report (Class A and B devices)
* Article 81 Periodic Safety Update Report (PSUR, Class C and D devices)
* Annex XIII Part B PMPF plan and PMPF evaluation report requirements
== Related documents ==
* [[In Vitro Diagnostic Medical Devices Regulation (EU) 2017/746]] Parent document
* [[MDR 2017/745 Articles 8386 & Annex XIV Part B: Post-Market Surveillance and PMCF]] Equivalent provision for medical devices
[[Category:In Vitro Diagnostics]]
[[Category:Post-Market Surveillance]]
[[Category:Clinical]]

View File

@ -0,0 +1,36 @@
{{RegDocument
| area_of_validity = EU
| scope = IVD
| document_type = Regulation Article Reference
| name = IVDR (EU) 2017/746 Article 56 & Annex XIII Part A: Performance Evaluation
| version = 2017/746
| source = https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32017R0746#d1e4774-1-1
| language = EN, DE, FR
| status = Current
| restricted_access = No
}}
Article 56 and Annex XIII Part A of the IVDR define the requirements for performance evaluation of IVDs. Performance evaluation is the IVD equivalent of clinical evaluation under the MDR.
== Summary ==
Manufacturers must demonstrate that their device achieves its intended performance through a systematic and planned process covering scientific validity, analytical performance, and clinical performance.
Key obligations:
* Article 56 Performance evaluation requirements and documentation
* Annex XIII Part A Performance evaluation plan (PEP) and performance evaluation report (PER)
The three pillars of performance evaluation:
* **Scientific validity** the association between the analyte and a clinical condition or physiological state
* **Analytical performance** the ability to correctly detect or measure a particular analyte (sensitivity, specificity, precision, etc.)
* **Clinical performance** the ability of the device to yield results correlated with a clinical condition in a defined target population
== Related documents ==
* [[In Vitro Diagnostic Medical Devices Regulation (EU) 2017/746]] Parent document
[[Category:In Vitro Diagnostics]]
[[Category:Clinical]]
[[Category:Performance Evaluation]]

View File

@ -0,0 +1,39 @@
{{RegDocument
| area_of_validity = EU
| scope = IVD
| document_type = Regulation Article Reference
| name = IVDR (EU) 2017/746 Articles 2426 & Annex VI: UDI
| version = 2017/746
| source = https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32017R0746#d1e3362-1-1
| language = EN, DE, FR
| status = Current
| restricted_access = No
}}
Articles 24 to 26 and Annex VI of the IVDR establish the Unique Device Identification (UDI) system for IVDs placed on the EU market. The framework is aligned with the MDR UDI system.
== Summary ==
The UDI system enables unambiguous identification and traceability of IVDs throughout the supply chain and in clinical laboratories.
Key obligations:
* Article 24 UDI assignment and registration in EUDAMED
* Article 25 UDI database (part of EUDAMED)
* Article 26 Registration of devices and economic operators in EUDAMED
* Annex VI Part C Requirements for the UDI carrier (label placement, symbology)
Application dates by device class:
* Class D: May 2022
* Class C: May 2023
* Class B and A (sterile): May 2025
* Class A (non-sterile): May 2027
== Related documents ==
* [[In Vitro Diagnostic Medical Devices Regulation (EU) 2017/746]] Parent document
* [[MDR 2017/745 Articles 2729 & Annex VI: Unique Device Identification (UDI)]] Equivalent provision for medical devices
[[Category:In Vitro Diagnostics]]
[[Category:Labelling]]
[[Category:Traceability]]

View File

@ -0,0 +1,34 @@
{{RegDocument
| area_of_validity = EU
| scope = MD
| document_type = Regulation Article Reference
| name = MDR 2017/745 Articles 8386 & Annex XIV Part B: Post-Market Surveillance and PMCF
| version = 2017/745
| source = https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32017R0745#d1e6354-1-1
| language = EN, DE, FR
| status = Current
| restricted_access = No
}}
Articles 83 to 86 of the MDR establish the mandatory post-market surveillance (PMS) system for all medical device manufacturers. Annex XIV Part B specifies the requirements for post-market clinical follow-up (PMCF).
== Summary ==
Manufacturers must plan, establish, document, implement, maintain, and update a PMS system proportionate to the risk class of the device.
Key obligations:
* Article 83 General PMS requirements and system structure
* Article 84 PMS plan (content requirements)
* Article 85 PMS report (required for Class I devices)
* Article 86 Periodic Safety Update Report (PSUR, required for Class IIa, IIb, III)
* Annex XIV Part B PMCF plan and PMCF evaluation report requirements
== Related documents ==
* [[Medical Device Regulation (EU) 2017/745]] Parent document
* [[IVDR (EU) 2017/746 Articles 7881 & Annex XIII Part B: Post-Market Performance Follow-Up]] Equivalent provision for IVDs
[[Category:Medical Devices]]
[[Category:Post-Market Surveillance]]
[[Category:Clinical]]

View File

@ -0,0 +1,38 @@
{{RegDocument
| area_of_validity = EU
| scope = MD
| document_type = Regulation Article Reference
| name = MDR 2017/745 Articles 2729 & Annex VI: Unique Device Identification (UDI)
| version = 2017/745
| source = https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32017R0745#d1e3455-1-1
| language = EN, DE, FR
| status = Current
| restricted_access = No
}}
Articles 27 to 29 and Annex VI of the MDR establish the Unique Device Identification (UDI) system for medical devices placed on the EU market.
== Summary ==
The UDI system enables unambiguous identification and traceability of devices throughout the supply chain and during clinical use.
Key obligations:
* Article 27 UDI assignment and registration in EUDAMED
* Article 28 UDI database (part of EUDAMED)
* Article 29 Registration of devices and economic operators in EUDAMED
* Annex VI Part C Requirements for the UDI carrier (label placement, symbology)
Application dates vary by device class:
* Class III and implantable: May 2021
* Class IIa and IIb: May 2023
* Class I: May 2025
== Related documents ==
* [[Medical Device Regulation (EU) 2017/745]] Parent document
* [[IVDR (EU) 2017/746 Articles 2426 & Annex VI: UDI]] Equivalent provision for IVDs
[[Category:Medical Devices]]
[[Category:Labelling]]
[[Category:Traceability]]

View File

@ -0,0 +1,40 @@
<includeonly>|-
| {{{topic|}}} || {{{category|}}} || {{{scope|}}} || {{{linked_document|}}} || {{{description|}}}
</includeonly><noinclude>
== Template:Reference ==
Use this template to add a row to a reference table. Place all <code>{{Reference}}</code> calls between a table header and the closing <code>|}</code>.
=== Usage ===
<pre>
{| class="wikitable"
! Topic / Keyword(s) !! Category / Chapter !! Scope !! Linked document !! Description
{{Reference
| topic =
| category =
| scope =
| linked_document =
| description =
}}
|}
</pre>
=== Fields ===
{| class="wikitable"
! Field !! Description !! Example values
|-
| <code>topic</code> || Keyword(s) or topic(s), comma-separated || Post-market surveillance, PMCF
|-
| <code>category</code> || Predefined chapter or database category || Clinical, Labelling, Quality Management
|-
| <code>scope</code> || Regulatory scope(s), comma-separated || MD, IVD, GMP, PV
|-
| <code>linked_document</code> || Link to the corresponding RegDocument page || <nowiki>[[MDR 2017/745]]</nowiki>
|-
| <code>description</code> || Short summary of what this reference covers || Defines requirements for PMCF per Annex XIV Part B.
|}
[[Category:Templates]]
</noinclude>

View File

@ -0,0 +1,88 @@
{{#set:
Area of validity={{{area_of_validity|}}}
|Scope={{{scope|}}}
|Document type={{{document_type|}}}
|Document name={{{name|}}}
|Version={{{version|}}}
|Source={{{source|}}}
|Language={{{language|}}}
|Status={{{status|}}}
|Restricted access={{{restricted_access|}}}
}}<includeonly>{| class="wikitable" style="float:right; clear:right; margin:0 0 1em 1em; width:320px; font-size:0.9em;"
! colspan="2" style="background:#f0f4f8; text-align:center;" | Document Information
|-
! Area of validity
| {{{area_of_validity|}}}
|-
! Scope(s)
| {{{scope|}}}
|-
! Document type
| {{{document_type|}}}
|-
! Document name
| {{{name|}}}
|-
! Version / Revision
| {{{version|}}}
|-
! Official source
| {{#if:{{{source|}}}|[{{{source}}} Official link]|—}}
|-
! Language(s)
| {{{language|}}}
|-
! Status
| {{{status|}}}
|-
! Restricted access
| {{{restricted_access|}}}
|}</includeonly><noinclude>
== Template:RegDocument ==
Use this template on any page that documents a regulatory document. Place it at the top of the page before any prose content.
This template also annotates each field as a [[Help:Semantic MediaWiki|Semantic MediaWiki]] property, enabling structured queries across all document pages.
=== Usage ===
<pre>
{{RegDocument
| area_of_validity =
| scope =
| document_type =
| name =
| version =
| source =
| language =
| status =
| restricted_access =
}}
</pre>
=== Fields ===
{| class="wikitable"
! Field !! Description !! Example values
|-
| <code>area_of_validity</code> || Region or country where the document applies || EU, USA, Global, DE
|-
| <code>scope</code> || Regulatory scope(s), comma-separated || MD, IVD — or NIS, GMP, PV, IMP, GEP, HM
|-
| <code>document_type</code> || Type of document || Regulation, Directive, Guideline, Scientific article, Treaty
|-
| <code>name</code> || Full official document name || Medical Device Regulation (EU) 2017/745
|-
| <code>version</code> || Version, revision or amendment || 2017/745, amended by 2023/607
|-
| <code>source</code> || URL to the official source || <nowiki>https://eur-lex.europa.eu/...</nowiki>
|-
| <code>language</code> || Available language(s) || EN, DE, FR
|-
| <code>status</code> || Validity status || Current, Draft, Final not promulgated, Superseded
|-
| <code>restricted_access</code> || Blocks copy/paste of content, referencing still allowed || Yes / No
|}
[[Category:Templates]]
</noinclude>

View File

@ -0,0 +1,9 @@
* Document Database
** Document_Index|All documents
** Special:Ask|Custom query
** Special:Categories|Browse categories
* navigation
** mainpage|mainpage-description
** Special:RecentChanges|recentchanges
** Special:Random|randompage

View File

@ -0,0 +1,49 @@
{{RegDocument
| area_of_validity = EU
| scope = MD
| document_type = Regulation
| name = Medical Device Regulation (EU) 2017/745
| version = 2017/745, amended by 2023/607
| source = https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32017R0745
| language = EN, DE, FR (all EU official languages)
| status = Current
| restricted_access = No
}}
The Medical Device Regulation (MDR) 2017/745 replaces the former Medical Device Directive (MDD 93/42/EEC) and the Active Implantable Medical Device Directive (AIMDD 90/385/EEC). It establishes a uniform regulatory framework for medical devices across EU member states, with a strong focus on clinical evidence, post-market surveillance, and traceability.
Full application started on 26 May 2021. The amendment 2023/607 introduced a phased transition period for legacy devices.
== Key references ==
{| class="wikitable" style="width:100%;"
! Topic / Keyword(s) !! Category / Chapter !! Scope !! Linked document !! Description
{{Reference
| topic = Post-market surveillance, PMCF
| category = Clinical
| scope = MD
| linked_document = [[MDR 2017/745 Articles 8386 & Annex XIV Part B: Post-Market Surveillance and PMCF]]
| description = Articles 8386 define the mandatory PMS system; Annex XIV Part B specifies PMCF requirements.
}}
{{Reference
| topic = Unique Device Identification, UDI, Traceability
| category = Labelling
| scope = MD
| linked_document = [[MDR 2017/745 Articles 2729 & Annex VI: Unique Device Identification (UDI)]]
| description = Articles 2729 and Annex VI establish the UDI system. All devices must carry a UDI carrier on label and packaging.
}}
{{Reference
| topic = Clinical evaluation, Clinical evidence
| category = Clinical
| scope = MD
| linked_document =
| description = Article 61 and Annex XIV Part A define the clinical evaluation process and documentation requirements (CER). ''Page not yet created.''
}}
|}
== Related documents ==
* [[In Vitro Diagnostic Medical Devices Regulation (EU) 2017/746]] Parallel regulation for IVDs
[[Category:Medical Devices]]
[[Category:EU Regulation]]