initial commit
This commit is contained in:
commit
21711bd5dd
253 changed files with 78415 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
0
.hugo_build.lock
Normal file
0
.hugo_build.lock
Normal file
14
README.md
Normal file
14
README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Programme pour les rencontres de Lure 2026
|
||||
|
||||
Vous êtes ici: https://forge.studio-variable.com/Julie/lure-2026
|
||||
|
||||
Liens:
|
||||
- Framapad: https://mensuel.framapad.org/p/lure-2026-web-to-print
|
||||
- Ressources : https://resources.julie-blanc.fr/list-of-resources/
|
||||
- Paged.js documentation: https://pagedjs.org/
|
||||
|
||||
## Workshop
|
||||
|
||||
11–12 janvier 2026 \
|
||||
Césure, Paris
|
||||
|
||||
14
archetypes/default.md
Normal file
14
archetypes/default.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: '{{ replace .File.ContentBaseName "-" " " | title }}'
|
||||
subtitle: ""
|
||||
type: "conference"
|
||||
tags:
|
||||
- ""
|
||||
- ""
|
||||
day: ""
|
||||
order: 1
|
||||
imgCover: ""
|
||||
imgBio: ""
|
||||
biographie: >
|
||||
**Ici le texte** de la biographie.
|
||||
---
|
||||
81
assets/style-print.css
Normal file
81
assets/style-print.css
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/* NE PAS TOUCHER CE CODE ------------------------------------- */
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-font-smoothing: antialiased;
|
||||
-o-font-smoothing: antialiased;
|
||||
}
|
||||
a {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
#btn-print{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* CODE POUR LA PAGE ----------------------------------------- */
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
|
||||
@page{
|
||||
size: 148mm 210mm;
|
||||
margin-top: 10mm;
|
||||
margin-bottom: 10mm;
|
||||
}
|
||||
|
||||
@page:left{
|
||||
margin-left: 10mm;
|
||||
margin-right: 12mm;
|
||||
@left-middle{
|
||||
content: counter(page);
|
||||
}
|
||||
}
|
||||
|
||||
@page:right{
|
||||
margin-left: 12mm;
|
||||
margin-right: 11mm;
|
||||
@right-middle{
|
||||
content: counter(page);
|
||||
}
|
||||
}
|
||||
|
||||
@page:first{
|
||||
@top-left-corner { content: none; }
|
||||
@top-left { content: none; }
|
||||
@top-center { content: none; }
|
||||
@top-right { content: none; }
|
||||
@top-right-corner { content: none; }
|
||||
@left-top { content: none; }
|
||||
@left-middle { content: none; }
|
||||
@left-bottom { content: none; }
|
||||
@right-top { content: none; }
|
||||
@right-middle { content: none; }
|
||||
@right-bottom { content: none; }
|
||||
@bottom-left-corner { content: none; }
|
||||
@bottom-left { content: none; }
|
||||
@bottom-center { content: none; }
|
||||
@bottom-right { content: none; }
|
||||
@bottom-right-corner { content: none; }
|
||||
}
|
||||
|
||||
.couverture{
|
||||
break-after: page;
|
||||
}
|
||||
|
||||
|
||||
/* VOTRE CODE ------------------------------------------------ */
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
:root{
|
||||
font-family: 'Sentient';
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
figure, img{
|
||||
width: 200px;
|
||||
}
|
||||
23
assets/style-screen.css
Normal file
23
assets/style-screen.css
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
@import "modules/base.css";
|
||||
|
||||
#btn-print{
|
||||
|
||||
color: IndianRed;
|
||||
border: 2px solid currentColor;
|
||||
background: Lavender;
|
||||
font-size: 30px;
|
||||
border-radius: 10px;
|
||||
|
||||
a{
|
||||
color: currentColor;
|
||||
text-decoration: none;
|
||||
padding: 0.75ch 1ch;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
figure, img{
|
||||
width: 200px;
|
||||
}
|
||||
8
content/edito.md
Normal file
8
content/edito.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: "Édito"
|
||||
type: "edito"
|
||||
---
|
||||
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quae provident perspiciatis explicabo. Quae cupiditate tempore neque tenetur incidunt eaque dolores eum illum adipisci vitae maiores hic ducimus ut impedit, dicta consequatur eveniet magni nisi laboriosam expedita voluptatum, sunt et sapiente repudiandae. Incidunt laboriosam error placeat iusto delectus eius eaque eos ex suscipit amet libero fuga reiciendis nesciunt nobis dolor repellendus, omnis ut, neque architecto praesentium iste? Nulla nostrum, adipisci maxime minus saepe labore inventore temporibus corporis. Dolore voluptatum esse illo quaerat distinctio, consequuntur molestiae placeat perspiciatis sed cumque! Facere amet aperiam voluptatibus maiores reiciendis, voluptate harum nisi incidunt corrupti, quam dolores! Accusantium corrupti, laudantium vel fugiat amet eveniet mollitia accusamus omnis adipisci modi reprehenderit eius dolorum nostrum quidem explicabo nemo animi praesentium. Quae quis porro iusto rem vel. Illo, numquam? Corporis unde ea eligendi corrupti molestiae dolorem architecto reprehenderit dolor qui, sed cumque labore at perferendis eos sapiente maiores. Eligendi, ipsa eaque doloribus excepturi voluptatem temporibus quis quam molestias unde consequuntur animi vitae accusamus, vero necessitatibus fugiat officiis! Iure velit incidunt reprehenderit voluptates, quia facere est. Nihil soluta, laboriosam laborum officiis libero neque asperiores, repellat vitae error maxime totam veniam vel hic ab, ducimus saepe tenetur excepturi officia iste eligendi.
|
||||
|
||||
Illo, numquam? Corporis unde ea eligendi corrupti molestiae dolorem architecto reprehenderit dolor qui, sed cumque labore at perferendis eos sapiente maiores. Eligendi, ipsa eaque doloribus excepturi voluptatem temporibus quis quam molestias unde consequuntur animi vitae accusamus, vero necessitatibus fugiat officiis! Iure velit incidunt reprehenderit voluptates, quia facere est. Nihil soluta, laboriosam laborum officiis libero neque asperiores, repellat vitae error maxime totam veniam vel hic ab, ducimus saepe tenetur excepturi officia iste eligendi.
|
||||
17
content/people/julies-durand.md
Normal file
17
content/people/julies-durand.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "Jules Durand"
|
||||
subtitle: "Jou – En quête de l’imaginaire typographique provençal "
|
||||
type: "conference"
|
||||
tags:
|
||||
- "design graphique"
|
||||
- "typographie"
|
||||
day: "mardi"
|
||||
order: 3
|
||||
imgCover: "72dpi-photo-jules.jpg"
|
||||
imgBio: "photo-chez-jou-jules.jpg"
|
||||
biographie: >
|
||||
**Jules Durand** est un designer graphique et dessinateur de caractères indépendant, travaillant à Toulouse, diplômé de l’Atelier national de recherches typographiques en 2022. Les caractères qu’il conçoit sont taillés pour le titrage et à l’épreuve du texte, convoquant des esthétiques allant du Moyen Âge au rétro-futurisme.
|
||||
---
|
||||
|
||||
Cette présentation reviendra sur la genèse du Tale of Type, personnifiant des caractères historiques pour faire naître une histoire anachronique de la typographie. Nous irons ensuite à la découverte des productions graphiques de Louis Jou, l’architecte du livre et des Baux-de-Provence dont Jules Durand revisite l’œuvre depuis 5 ans.
|
||||
|
||||
19
content/people/louis-rigaud.md
Normal file
19
content/people/louis-rigaud.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
title: "Louis Rigaud"
|
||||
subtitle: "Le jeu *sous toutes* ses formes "
|
||||
type: "conference"
|
||||
tags:
|
||||
- "design graphique"
|
||||
day: "lundi"
|
||||
order: 2
|
||||
imgCover: "familleacrobate-louisrigaud-anouckboisrobert-helium-2018-2.jpg"
|
||||
imgBio: "portrait-louisrigaud.jpg"
|
||||
website: "ludocube.fr"
|
||||
contenu: ""
|
||||
biographie: >
|
||||
**Louis Rigaud**, en duo avec Anouck Boisrobert, crée des livres pop-up aux éditions hélium depuis 2009. Il réalise aussi des films d’animation, du web, des affiches, de la scénographie… Passionné par le code, il programme ses propres jeux vidéo.
|
||||
---
|
||||
|
||||
|
||||
Le jeu sous toutes ses formes a façonné mon imaginaire, il infuse aujourd’hui mon travail, autant dans mon processus créatif que dans mes réalisations. Du livre animé au jeu vidéo en passant par la scénographie ou la création d’ateliers pour enfants, j’essayerai de définir l’approche ludique qui guide mes choix artistiques.
|
||||
|
||||
16
content/people/maud-guerche.md
Normal file
16
content/people/maud-guerche.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: "Maud Guerche"
|
||||
subtitle: "Double je"
|
||||
type: "conference"
|
||||
tags:
|
||||
- "design graphique"
|
||||
day: "lundi"
|
||||
order: 1
|
||||
imgCover: "bora-bord-identite_ring_katharsy-03-maud-guerche.jpg"
|
||||
imgBio: "mguerche_lucaprigione_112-maud-guerche.jpg"
|
||||
website: "bora-bord.com"
|
||||
biographie: >
|
||||
**Maud Guerche** est designer graphique, typographique & signalétique. Diplômée de la HEAR en 2009, elle répond à des commandes dans le cadre de l'atelier bora-bord et développe une recherche artistique en poursuivant ses explorations sur le visible. Elle travaille la lettre à toutes les échelles, s'intéressant à provoquer le sensible, interrogeant les codes entre ce qui est vu, su, lu.
|
||||
---
|
||||
|
||||
Aborder le rapport au jeu dans ma pratique de designer, à travers les créations de ces quinze dernières années, mettant en jeu de la matière graphique et poétique. Jouer avec les formes, construire ou concevoir par le jeu, ainsi que le jeu comme thème de travail, et le processus de création avec une metteuse en scène.
|
||||
16
content/people/pierre-fourny.md
Normal file
16
content/people/pierre-fourny.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: "Pierre Fourny"
|
||||
subtitle: "L'écriture acrobatique "
|
||||
type: "conference"
|
||||
tags:
|
||||
- "design graphique"
|
||||
day: "vendredi"
|
||||
order: 1
|
||||
imgCover: "conference-fac-alis.jpg"
|
||||
imgBio: "alis_emmanuel-pierrot_portraitaveclettres.jpg"
|
||||
website: "alislab.fr"
|
||||
biographie: >
|
||||
**Pierre Fourny** est auteur metteur en scène pour ALIS (spectacles). Au tournant des années 2000 il invente la « Poésie à 2 mi-mots » qui consiste, grâce à une police de caractères spécifique, la Police coupable, à montrer que les mots, coupés en 2 horizontalement, contiennent la moitié d'autres mots.
|
||||
---
|
||||
|
||||
L'écriture acrobatique, conférence-spectacle, met en parallèle le parcours d'un créateur de spectacles, Pierre Fourny, et rien moins que l'histoire du langage et de l'écriture alphabétique. L'accent sera mis sur la relation entre le fonctionnement de la scène et le travail sur la forme des lettres à partir de différentes polices de caractères impliquant un fonctionnement spectaculaire.
|
||||
8
hugo.toml
Normal file
8
hugo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
baseURL = 'https://example.org/'
|
||||
languageCode = 'fr-fr'
|
||||
title = 'Lure 2026'
|
||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap"]
|
||||
|
||||
[markup]
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
44
layouts/_default/baseof.html
Normal file
44
layouts/_default/baseof.html
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ .Title }}</title>
|
||||
|
||||
<!-- FONTS -->
|
||||
<link href="/fonts/dsentient/stylesheet.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Charge le CSS pour l'affichage écran -->
|
||||
{{ $styleScreen := resources.Get "style-screen.css" }}
|
||||
{{ if $styleScreen }}
|
||||
<link id="style-screen" rel="stylesheet" type="text/css" href="{{ $styleScreen.RelPermalink }}">
|
||||
{{ end }}
|
||||
{{ $stylePrint := resources.Get "style-print.css" }}
|
||||
{{ if $styleScreen }}
|
||||
<link id="style-print" rel="stylesheet" type="text/css" href="{{ $stylePrint.RelPermalink }}" media="print">
|
||||
{{ end }}
|
||||
</head>
|
||||
<body>
|
||||
<button id="btn-print"><a href="/?print=true">Print</a></button>
|
||||
|
||||
{{ block "main" . }}{{ end }}
|
||||
|
||||
|
||||
<script>
|
||||
// Charge le module csspageweaver si ?print=true
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('print') === 'true') {
|
||||
// Retire le CSS d'affichage écran
|
||||
const styleScreen = document.getElementById('style-screen');
|
||||
if (styleScreen) {
|
||||
styleScreen.remove();
|
||||
}
|
||||
|
||||
// Charge le script
|
||||
const script = document.createElement('script');
|
||||
script.src = '/csspageweaver/main.js';
|
||||
script.type = 'module';
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
82
layouts/index.html
Normal file
82
layouts/index.html
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
{{ define "main" }}
|
||||
<section class="couverture">
|
||||
|
||||
<h1 class="title">Nos vertiges</h1>
|
||||
<h2 class="sutitle">Échelles, formats, mesures et démesure</h2>
|
||||
<p class="date">22–27 août 2026</p>
|
||||
<p class="rencontres">74<sup>o</sup> rencontres internationales de Lure</p>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="edito">
|
||||
{{ range where .Site.RegularPages "Type" "edito" }}
|
||||
<h2>{{ .Title }}</h2>
|
||||
<div class="edito-content">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</section>
|
||||
|
||||
{{ $pages := where .Site.RegularPages "Section" "people" }}
|
||||
{{ $days := slice "lundi" "mardi" "mercredi" "jeudi" "vendredi" "samedi" "dimanche" }}
|
||||
|
||||
{{ range $days }}
|
||||
{{ $day := . }}
|
||||
{{ $dayPages := where $pages "Params.day" $day }}
|
||||
|
||||
{{ if $dayPages }}
|
||||
<section class="day" id="section-{{ $day }}">
|
||||
<h2 class="day-title">{{ $day }}</h2>
|
||||
|
||||
{{ range sort $dayPages "Params.order" }}
|
||||
<article class="person" id="{{ .File.BaseFileName }}">
|
||||
|
||||
<figure><img src="/images/{{ .Params.imgCover }}"></figure>
|
||||
|
||||
<h3 class="title">{{ .Title | markdownify }}</h3>
|
||||
<h4 class="subtitle">{{ .Params.subtitle | markdownify }}</h4>
|
||||
|
||||
<div class="type-tags-group">
|
||||
<p class="type">{{ .Params.type }}</p>
|
||||
<ul class="tags">
|
||||
{{ range .Params.tags }}<li class="tag">{{ . }}</li>{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
|
||||
<figure><img src="/images/{{ .Params.imgBio }}"></figure>
|
||||
|
||||
<div class="biographie">
|
||||
<p>{{ .Params.biographie | markdownify }}</p>
|
||||
</div>
|
||||
|
||||
{{ with .Params.website }}
|
||||
<p class="website"><a href="https://{{ . }}" target="_blank">{{ . }}</a></p>
|
||||
{{ end }}
|
||||
|
||||
</article>
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
|
||||
<div class="4e-couverture">
|
||||
|
||||
<h2>Remerciments et colophon</h2>
|
||||
|
||||
<div class="remerciement">
|
||||
<p>
|
||||
Les rencontres internationales de Lure remercient...
|
||||
</p>
|
||||
<p>
|
||||
Merci à ...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
1
public/csspageweaver/.gitignore
vendored
Normal file
1
public/csspageweaver/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
104
public/csspageweaver/.gitlab-ci.yml
Normal file
104
public/csspageweaver/.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
stages:
|
||||
- prepare
|
||||
- release
|
||||
|
||||
# This job compile CSS Page Weaver and few plugins
|
||||
prepare_job:
|
||||
stage: prepare
|
||||
image: ubuntu:latest
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
before_script:
|
||||
- apt-get update && apt-get install -y git curl jq zip bash
|
||||
- git config --global user.email "csspageweaver@csspageweaver.org"
|
||||
- git config --global user.name "Automated Releaser"
|
||||
script:
|
||||
|
||||
- |
|
||||
# Store Job ID since next stage (release_job) link to current job artifacts
|
||||
echo "ARTIFACTS_JOB=$CI_JOB_ID" >> build.env
|
||||
|
||||
- |
|
||||
# create plugins folder if needed
|
||||
mkdir -p plugins
|
||||
|
||||
- |
|
||||
# Set plugins list
|
||||
REPO_LIST=(
|
||||
"baseline"
|
||||
"grid"
|
||||
"imposition"
|
||||
"marginBox"
|
||||
"spread"
|
||||
"previewPage"
|
||||
"reloadInPlace"
|
||||
)
|
||||
|
||||
- |
|
||||
# Add listed plugins as git subtree. Need deployment tokens if repos are private
|
||||
for REPO in "${REPO_LIST[@]}"; do
|
||||
SUBTREE_URL="https://gitlab.com/csspageweaver/plugins/${REPO}.git"
|
||||
git subtree add --prefix="plugins/$REPO" "$SUBTREE_URL" main --squash
|
||||
done
|
||||
|
||||
- |
|
||||
# Define the pluginsParameters object
|
||||
PLUGINS_PARAMETERS='{
|
||||
"baseline": {
|
||||
"size": 16,
|
||||
"position": 0
|
||||
},
|
||||
"reloadInPlace": {
|
||||
"blur": false,
|
||||
"behavior": "instant"
|
||||
}
|
||||
}'
|
||||
|
||||
- |
|
||||
# Add plugin list to manifest
|
||||
MANIFEST_PATH="manifest.json"
|
||||
PLUGINS_ENTRY=$(jq --arg repos "${REPO_LIST[*]}" '.plugins = ($repos | split(" "))' "$MANIFEST_PATH")
|
||||
echo "$PLUGINS_ENTRY" > "$MANIFEST_PATH"
|
||||
|
||||
- |
|
||||
# Add plugin parameters to manifest
|
||||
PARAMETERS_PLUGINS_ENTRY=$(jq --argjson pluginsParameters "$PLUGINS_PARAMETERS" '.pluginsParameters = $pluginsParameters' "$MANIFEST_PATH")
|
||||
echo "$PARAMETERS_PLUGINS_ENTRY" > "$MANIFEST_PATH"
|
||||
|
||||
- |
|
||||
# create folder
|
||||
mkdir -p csspageweaver
|
||||
|
||||
# Copy releveant folder and file to a csspageweaver folder
|
||||
cp -r interface/ lib/ modules/ plugins/ csspageweaver/
|
||||
cp main.js manifest.json README.md .gitignore csspageweaver/
|
||||
|
||||
|
||||
artifacts:
|
||||
name: csspageweaver-core-$CI_COMMIT_TAG
|
||||
paths:
|
||||
- csspageweaver/
|
||||
reports:
|
||||
dotenv: build.env
|
||||
expire_in: never
|
||||
|
||||
# This job create a release based on previous artifact
|
||||
release_job:
|
||||
stage: release
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
needs:
|
||||
- job: prepare_job
|
||||
artifacts: true
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
script:
|
||||
- echo "running release_job for $CI_COMMIT_TAG "
|
||||
release:
|
||||
name: 'Release $CI_COMMIT_TAG'
|
||||
tag_name: '$CI_COMMIT_TAG'
|
||||
ref: '$CI_COMMIT_SHA'
|
||||
description: 'Auto release'
|
||||
assets:
|
||||
links:
|
||||
- name: '👉 CSS Page Weaver with plugins embed'
|
||||
url: 'https://gitlab.com/csspageweaver/csspageweaver/-/jobs/$ARTIFACTS_JOB/artifacts/download'
|
||||
177
public/csspageweaver/README.md
Normal file
177
public/csspageweaver/README.md
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# CSS Page Weaver
|
||||
|
||||

|
||||
|
||||
CSS Page Weaver is a browser-based publishing tool, made up of [PagedJs](https://pagedjs.org/about/) library and modulars additionals features. While it's design to ease installation for beginners CSS Page Weaver also bring elegant and extendable solution for more advanced users.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **Streamlined and Ready-to-Use**: Get started quickly with a standardized way to integrate features.
|
||||
- **Extensive Plugin Library**: No need to reinvent the wheel with the available plugins.
|
||||
- **WYSIWYG Editor**: Streamline your design pratice with an extandable interface.
|
||||
- **Create and Share Plugins**: Develop and share your own plugins easily.
|
||||
|
||||
## ⛵ Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- A local web server
|
||||
|
||||
### 🪴 Installation (fastest way)
|
||||
|
||||
If you feel a bit lost with following instruction, you should probably look at the [CSS Page Weaver compiled with a few plugins](https://gitlab.com/csspageweaver/csspageweaver/-/releases)
|
||||
|
||||
Integrate it at the root level of your page and add a link to `csspageweaver` main _module_ into your HTML template
|
||||
|
||||
```html
|
||||
<script src="/csspageweaver/main.js" type="module"></script>
|
||||
```
|
||||
|
||||
*Do you need to also install PagedJs? Nope! CSS Page Weaver already embed it.*
|
||||
|
||||
### Use
|
||||
|
||||
Run a simple server. That's it!
|
||||
|
||||
### Boilerplate
|
||||
|
||||
Don't have a project to test it? There is [a ready-to-use boilerplate](https://gitlab.com/csspageweaver//boilerplate). Download, unzip & run a server!
|
||||
|
||||
|
||||
## 🚀 Going further
|
||||
|
||||
### 🌲 Installation (complete way)
|
||||
|
||||
Released version on CSS Page Weaver is compiled with few plugins. You can install the package yourself for greater control.
|
||||
|
||||
#### Clone CSS Page Weaver repo in your project [option A].
|
||||
|
||||
```bash
|
||||
# With HTTPS
|
||||
git clone https://gitlab.com/csspageweaver/csspageweaver.git
|
||||
|
||||
# With SSH
|
||||
git clone git@gitlab.com:csspageweaver/csspageweaver.git
|
||||
```
|
||||
#### Clone CSS Page Weaver as a git subtree [option B].
|
||||
|
||||
Subtree are great to:
|
||||
- embed CSS Page Weaver repo in another repo
|
||||
- get updates
|
||||
|
||||
```bash
|
||||
git subtree add --prefix csspageweaver/ git@gitlab.com:csspageweaver/cssPageWeaver.git --squash
|
||||
```
|
||||
|
||||
### 🔌 Dependencies
|
||||
|
||||
CSS Page Weaver is designed to work with plugins. In this complete installation, you need to install plugins by yourself. If you've downloaded [the last release of CSS Page Weaver](https://gitlab.com/csspageweaver/csspageweaver/-/releases), few plugins are already embedded.
|
||||
|
||||
**Here is a [list of all plugins](https://gitlab.com/csspageweaver//plugins) known.**
|
||||
|
||||
**Steps**
|
||||
|
||||
1. Download and place plugin folder in `csspageweaver/plugins`
|
||||
2. Add plugin to manifest
|
||||
|
||||
#### Installation as subtree
|
||||
|
||||
We prefer to install plugins using Git Subtree because it allows us to easily preserve the filiation link with the plugin directory (and to obtain updates!).
|
||||
|
||||
Bear in mind, if you're not comfortable with command lines, that *step 1* can easily be replaced by a simple *download, drag and drop*.
|
||||
|
||||
Otherwise, here is how it works.
|
||||
|
||||
##### (Step 1) Clone plugin as a subtree
|
||||
|
||||
Install plugin as a submodule of `csspageweaver`
|
||||
|
||||
```bash
|
||||
git subtree add --prefix="csspageweaver/plugins/{{PLUGIN_FOLDER_NAME}}" git@gitlab.com:csspageweaver/plugins/{{PLUGIN_NAME}}.git --squash
|
||||
```
|
||||
|
||||
##### (Step 2) Add plugin to manifest
|
||||
|
||||
Almost done. Add a mention to `csspageweaver/manifest.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": [
|
||||
// existing plugin,
|
||||
"PLUGIN_FOLDER_NAME"
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Look at complete [plugins list](https://gitlab.com/csspageweaver/plugins) and [plugins installation guide](https://gitlab.com/csspageweaver/csspageweaver/-/wikis/design/plugins/install)
|
||||
|
||||
#### Update as subtree
|
||||
|
||||
This is where Git subtrees are wonderful
|
||||
|
||||
```bash
|
||||
git subtree pull --prefix="csspageweaver/plugins/{{PLUGIN_NAME}}" main --squash
|
||||
```
|
||||
|
||||
A bit dazed? Don't worry. Once again, you can update your plugins with your favorite *download, unzip, drag and drop* shady method.
|
||||
|
||||
### 🎁 Package manager
|
||||
|
||||
You already like the principle of subtrees, but you think (rightly) that the multiplication of command lines can be a bit tedious in the long run? Installation and dependencies can me handle with our [Package Manager](https://gitlab.com/csspageweaver/package-manager)
|
||||
|
||||
**Install CSS Page Weaver and plugins with package manager**
|
||||
|
||||
```bash
|
||||
./weaver_manager.sh --install
|
||||
```
|
||||
|
||||
**Get update with package manager**
|
||||
|
||||
```bash
|
||||
./weaver_manager.sh --pull
|
||||
```
|
||||
|
||||
See [Package Manager repository](https://gitlab.com/csspageweaver-toolkit/package_manager) and [further documentation on managing your installation](https://gitlab.com/csspageweaver/csspageweaver/-/wikis/maintain_and_develop/core/5-manage_csspageweaver_integration)
|
||||
|
||||
|
||||
|
||||
## 🔄 Customization
|
||||
|
||||
### Basic Information
|
||||
|
||||
Edit `csspageweaver/manifest.json` to declare:
|
||||
- Plugins
|
||||
- Plugins configuration
|
||||
- Stylesheets
|
||||
- Your custom hooks
|
||||
|
||||
### CSS Page Weaver behavior (advanced)
|
||||
|
||||
Edit `csspageweaver/main.js` to:
|
||||
- disable Common dictionary
|
||||
- disable Interface
|
||||
- Choose render method
|
||||
|
||||
## 🎓 Documentation
|
||||
|
||||
A complete [documentation is available](https://gitlab.com/csspageweaver/csspageweaver/-/wikis/home)
|
||||
|
||||
## 📝 License
|
||||
|
||||
This project is licensed under the MIT License
|
||||
|
||||
## 👏 Acknowledgements
|
||||
|
||||
CSS Page Weaver is based on [PagedJs](https://pagedjs.org/about/) by Coko Foundation.
|
||||
|
||||
CSS Page Weaver is an original idea of Julie Blanc ehanced by Benjamin G.
|
||||
Julien Taquet was a great help in reimagining the rendering module. Finally, Nicolas Taffin and Julien Bidoret helped to oversee this tool.
|
||||
|
||||
All CSS Page Weaver plugins remains linked to their original creators.
|
||||
Without them, GUI would remain an empty shell. Thanks 🙏
|
||||
|
||||
## 🙌 Contributing
|
||||
|
||||
Features and documentation requests are welcome! Feel free to check the [issues page](https://gitlab.com/csspageweaver/csspageweaver/-/issues).
|
||||
|
||||
Contributions must follow our [code of conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/)
|
||||
46
public/csspageweaver/interface/css/grid.css
Normal file
46
public/csspageweaver/interface/css/grid.css
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
@media print {
|
||||
|
||||
|
||||
|
||||
/* Size and marin for all pages ------------- */
|
||||
@page {
|
||||
size: 148mm 210mm;
|
||||
margin-top: 20mm;
|
||||
margin-bottom: 20mm;
|
||||
bleed: 6mm;
|
||||
marks: crop;
|
||||
@bottom-center{
|
||||
content: counter(page);
|
||||
}
|
||||
}
|
||||
|
||||
figure, img{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
*{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
#image-full{
|
||||
--pagedjs-full-page: spread;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#image-full img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
|
||||
.pagedjs_note{
|
||||
color: red;
|
||||
}
|
||||
127
public/csspageweaver/interface/css/interface.css
Normal file
127
public/csspageweaver/interface/css/interface.css
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/* CSS for Paged.js interface – v0.2
|
||||
Julie Blanc - 2020
|
||||
MIT License https://opensource.org/licenses/MIT
|
||||
A simple stylesheet to see pages on screen (with baseline included)
|
||||
Adapted for this project */
|
||||
|
||||
|
||||
/* Change the look */
|
||||
:root {
|
||||
--color-background: #efefef;
|
||||
--color-pageSheet: #cfcfcf;
|
||||
--color-pageBox: violet;
|
||||
--color-paper: white;
|
||||
--pagedjs-crop-color: #000;
|
||||
--pagedjs-crop-stroke: 1px;
|
||||
--color-preview: #222;
|
||||
|
||||
--pagedjs-header-height: 80px;
|
||||
|
||||
/* --pagedjs-bleed-left-right: 0mm!important; */
|
||||
}
|
||||
|
||||
.pagedjs_marks-crop{
|
||||
z-index: 999999999999;
|
||||
}
|
||||
|
||||
/* To define how the book look on the screen: */
|
||||
@media screen, pagedjs-ignore {
|
||||
body {
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.pagedjs_pages {
|
||||
display: flex;
|
||||
width: calc(var(--pagedjs-width) * 2);
|
||||
flex: 0;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 auto;
|
||||
margin-top: var(--pagedjs-header-height);
|
||||
}
|
||||
|
||||
.pagedjs_page {
|
||||
background-color: var(--color-paper);
|
||||
box-shadow: 0 0 0 1px var(--color-pageSheet);
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
|
||||
.pagedjs_first_page {
|
||||
margin-left: calc(var(--pagedjs-width) - var(--pagedjs-bleed-left));
|
||||
}
|
||||
|
||||
.pagedjs_page:last-of-type {
|
||||
margin-bottom: 10mm;
|
||||
}
|
||||
|
||||
.pagedjs_pagebox{
|
||||
box-shadow: 0 0 0 1px var(--color-pageBox);
|
||||
}
|
||||
|
||||
.pagedjs_left_page{
|
||||
z-index: 20;
|
||||
width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width))!important;
|
||||
}
|
||||
|
||||
/* .pagedjs_left_page .pagedjs_sheet{
|
||||
z-index: 20;
|
||||
width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width) + 1px)!important;
|
||||
} */
|
||||
|
||||
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-middle{
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.pagedjs_right_page{
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
left: calc(var(--pagedjs-bleed-left)*-1);
|
||||
}
|
||||
|
||||
/* show the margin-box */
|
||||
|
||||
.pagedjs_margin-top-left-corner-holder,
|
||||
.pagedjs_margin-top,
|
||||
.pagedjs_margin-top-left,
|
||||
.pagedjs_margin-top-center,
|
||||
.pagedjs_margin-top-right,
|
||||
.pagedjs_margin-top-right-corner-holder,
|
||||
.pagedjs_margin-bottom-left-corner-holder,
|
||||
.pagedjs_margin-bottom,
|
||||
.pagedjs_margin-bottom-left,
|
||||
.pagedjs_margin-bottom-center,
|
||||
.pagedjs_margin-bottom-right,
|
||||
.pagedjs_margin-bottom-right-corner-holder,
|
||||
.pagedjs_margin-right,
|
||||
.pagedjs_margin-right-top,
|
||||
.pagedjs_margin-right-middle,
|
||||
.pagedjs_margin-right-bottom,
|
||||
.pagedjs_margin-left,
|
||||
.pagedjs_margin-left-top,
|
||||
.pagedjs_margin-left-middle,
|
||||
.pagedjs_margin-left-bottom {
|
||||
box-shadow: 0 0 0 1px inset var(--color-marginBox);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.note {
|
||||
float: footnote;
|
||||
}
|
||||
|
||||
/* define the position of the footnote on the page (only bottom is possible for now) */
|
||||
|
||||
@page {
|
||||
@footnote {
|
||||
float: bottom;
|
||||
}
|
||||
}
|
||||
532
public/csspageweaver/interface/css/panel.css
Normal file
532
public/csspageweaver/interface/css/panel.css
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
@import url("../fonts/IBM_Plex_Mono/stylesheet.css");
|
||||
|
||||
@media print{
|
||||
csspageweaver-gui{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen, pagedjs-ignore {
|
||||
|
||||
|
||||
/* TOGGLE PANEL ----------------------------------- */
|
||||
#cssPageWeaver_toggle-panel ~ #cssPageWeaver_panel{ display: none; }
|
||||
#cssPageWeaver_toggle-panel + label #panel-open{ display: none; }
|
||||
|
||||
#cssPageWeaver_toggle-panel:checked ~ #cssPageWeaver_panel{ display: block; }
|
||||
#cssPageWeaver_toggle-panel:checked + label #panel-open{ display: block; }
|
||||
#cssPageWeaver_toggle-panel:checked + label #panel-closed{ display: none; }
|
||||
|
||||
#cssPageWeaver_toggle-panel{ display: none; }
|
||||
|
||||
|
||||
/* STYLE ----------------------------------- */
|
||||
:root{
|
||||
--cssPageWeaver-font:'IBM Plex Mono';
|
||||
--cssPageWeaver-size: 16px;
|
||||
--cssPageWeaver-fixed: 30px;
|
||||
|
||||
/* --cssPageWeaver-color-border: #8fb6b0;
|
||||
--cssPageWeaver-color-accent: #EE6C4D;
|
||||
--cssPageWeaver-color-accent-hover: #852811;
|
||||
--cssPageWeaver-color-text: #464f4e;
|
||||
--cssPageWeaver-color-bg: #f8f8f2; */
|
||||
|
||||
|
||||
/* --cssPageWeaver-color-accent: #EE6C4D;
|
||||
--cssPageWeaver-color-accent-hover: #852811;
|
||||
--cssPageWeaver-color-border: #86766e;
|
||||
--cssPageWeaver-color-text: #4f4946;
|
||||
--cssPageWeaver-color-bg: #efefef; */
|
||||
|
||||
--cssPageWeaver-color-accent: RebeccaPurple;
|
||||
--cssPageWeaver-color-accent-hover: BlueViolet;
|
||||
--cssPageWeaver-color-border: #836e86;
|
||||
--cssPageWeaver-color-border-light: #999999;
|
||||
--cssPageWeaver-color-text: #4e464f;
|
||||
--cssPageWeaver-color-bg: #f1f0f0;
|
||||
|
||||
--cssPageWeaver-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
/* Toggle button -------------------------------------------- */
|
||||
|
||||
#cssPageWeaver_toggle-panel + label{
|
||||
background-color: var(--cssPageWeaver-color-bg);
|
||||
border: 2px solid var(--cssPageWeaver-color-border);
|
||||
border-radius: var(--cssPageWeaver-color-radius);
|
||||
|
||||
--size: 30px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.25em;
|
||||
|
||||
color: var(--cssPageWeaver-color-text);
|
||||
//border-radius: var(--cssPageWeaver-radius);
|
||||
|
||||
transition: border-radius .25s ease-in-out;
|
||||
|
||||
}
|
||||
|
||||
#cssPageWeaver_toggle-panel:checked + label{
|
||||
border-top-left-radius: var(--cssPageWeaver-radius);
|
||||
}
|
||||
|
||||
|
||||
/* CONTAINER -------------------------------------------- */
|
||||
|
||||
csspageweaver-gui *{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
csspageweaver-gui{
|
||||
font-family: var(--cssPageWeaver-font);
|
||||
color: var(--cssPageWeaver-color-text);
|
||||
font-size: var(--cssPageWeaver-size);
|
||||
}
|
||||
|
||||
csspageweaver-gui label,
|
||||
csspageweaver-gui button{
|
||||
cursor: pointer!important;
|
||||
}
|
||||
|
||||
#cssPageWeaver_toggle-panel + label{
|
||||
position: fixed;
|
||||
left: var(--cssPageWeaver-fixed);
|
||||
top: var(--cssPageWeaver-fixed);
|
||||
width: 30px;
|
||||
left: 30px;
|
||||
z-index: 9000;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel{
|
||||
width: 296px;
|
||||
background-color: var(--cssPageWeaver-color-bg);
|
||||
border: 2px solid var(--cssPageWeaver-color-border);
|
||||
border-radius: var(--cssPageWeaver-radius);
|
||||
position: fixed;
|
||||
left: var(--cssPageWeaver-fixed);
|
||||
top: var(--cssPageWeaver-fixed);
|
||||
z-index: 8900;
|
||||
padding: 1em 1em .5em 1em;
|
||||
|
||||
max-height: 80vh;
|
||||
max-height: calc(100vh - 5rem);
|
||||
overflow: scroll;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* panel-group ------------------------------------- */
|
||||
|
||||
|
||||
#cssPageWeaver_panel .panel-group{
|
||||
margin: 0;
|
||||
padding-top: 1.25em;
|
||||
}
|
||||
#cssPageWeaver_panel .panel-group:not(:last-of-type){
|
||||
border-bottom: 1px solid var(--cssPageWeaver-color-border);
|
||||
padding-bottom: 1.25em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* panel-group-title ------------------------------------- */
|
||||
|
||||
#cssPageWeaver_panel h1,
|
||||
#cssPageWeaver_panel button,
|
||||
#cssPageWeaver_panel p,
|
||||
#cssPageWeaver_panel svg,
|
||||
#cssPageWeaver_panel label{
|
||||
all: initial;
|
||||
all: unset;
|
||||
}
|
||||
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title{
|
||||
display: grid;
|
||||
grid-template-columns: auto 1ch 1fr;
|
||||
grid-gap: .5ch;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel h1 span{
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title:not(:empty) + *{
|
||||
margin-top: 1.25em;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title h1{
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title input{
|
||||
display: none;
|
||||
}
|
||||
#cssPageWeaver_panel .panel-group-title label{
|
||||
grid-column: -1;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title label span{
|
||||
/* color: var(--cssPageWeaver-color-accent); */
|
||||
font-size: 16px;
|
||||
border: 1.5px solid var(--cssPageWeaver-color-accent);
|
||||
padding: 0.25ch 1ch 0.5ch 1ch;
|
||||
min-width: 4ch;
|
||||
text-align: center;
|
||||
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
border-radius: var(--cssPageWeaver-radius);
|
||||
|
||||
background-color: var(--cssPageWeaver-color-accent);
|
||||
border-color: var(--cssPageWeaver-color-accent);
|
||||
color: var(--cssPageWeaver-color-bg);
|
||||
|
||||
font-style: italic;
|
||||
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title label span:hover{
|
||||
background-color: var(--cssPageWeaver-color-accent-hover);
|
||||
border-color: var(--cssPageWeaver-color-accent-hover);
|
||||
color: var(--cssPageWeaver-color-bg);
|
||||
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title input:not(:checked) + label[id*="toggle"] span.button-see{
|
||||
background-color: var(--cssPageWeaver-color-bg);
|
||||
color: var(--cssPageWeaver-color-accent);
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title input:checked + label[id*="toggle"] span.button-hide{
|
||||
background-color: var(--cssPageWeaver-color-bg);
|
||||
color: var(--cssPageWeaver-color-accent);
|
||||
}
|
||||
|
||||
/* INFO BOX ---------------------------------- */
|
||||
#cssPageWeaver_panel .panel-group-title details{
|
||||
position: relative;
|
||||
}
|
||||
#cssPageWeaver_panel .panel-group-title summary{
|
||||
color: var(--cssPageWeaver-color-text);
|
||||
//border: 1px solid black;
|
||||
//border-radius: 1rem;
|
||||
width: 1rem;
|
||||
font-size: .8em;
|
||||
height: 1rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
opacity: .3;
|
||||
line-height: 160%;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title details summary::marker {
|
||||
display: none;
|
||||
content: "";
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title details p{
|
||||
position: absolute;
|
||||
background: var(--cssPageWeaver-color-bg);
|
||||
padding: .5ch;
|
||||
width: max-content;
|
||||
max-width: 10rem;
|
||||
font-size: .7rem;
|
||||
margin-top: .5ch;
|
||||
border: 1px solid var(--cssPageWeaver-color-accent);
|
||||
border-radius: .2ch;
|
||||
top: -50%;
|
||||
left: 2em;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
|
||||
|
||||
/* panel-group-values ---------------------------------- */
|
||||
|
||||
#cssPageWeaver_panel .panel-group-values{
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
/* INPUT NUMBER ---------------------------------- */
|
||||
|
||||
|
||||
#cssPageWeaver_panel input[type="number"]{
|
||||
font-family: var(--cssPageWeaver-font)!important;
|
||||
color: var(--cssPageWeaver-color-text);
|
||||
width: 8ch;
|
||||
background-color: transparent!important;
|
||||
border: 1px solid var(--cssPageWeaver-color-text);
|
||||
font-size: calc(var(--cssPageWeaver-size)*0.9);
|
||||
padding-left: 0.5ch;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel input[type="number"]:focus,
|
||||
#cssPageWeaver_panel input[type="number"]:focus-visible{
|
||||
border: 1px solid var(--cssPageWeaver-color-accent)!important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
/* INPUT CHECKBOX ---------------------------------- */
|
||||
|
||||
#cssPageWeaver_panel input[type="checkbox"],
|
||||
#cssPageWeaver_panel input[type="radio"]{ display: none; }
|
||||
|
||||
#cssPageWeaver_panel input + label:not([id*="toggle"])::before{
|
||||
content: "";
|
||||
--size: 12px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
border: 1.5px solid var(--cssPageWeaver-color-text);
|
||||
margin-right: 1ch;
|
||||
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel input:checked + label{
|
||||
color: var(--cssPageWeaver-color-accent);
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel input:checked + label::before{
|
||||
border: 5px solid var(--cssPageWeaver-color-accent);
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel input + label:hover{
|
||||
color: var(--cssPageWeaver-color-accent-hover);
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel input + label:hover::before{
|
||||
border: 1.5px solid var(--cssPageWeaver-color-accent-hover);
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel input[type="radio"]:checked + label:hover::before{
|
||||
border: 5px solid var(--cssPageWeaver-color-accent);
|
||||
}
|
||||
#cssPageWeaver_panel input[type="radio"]:checked + label:hover{
|
||||
color: var(--cssPageWeaver-color-accent);
|
||||
}
|
||||
#cssPageWeaver_panel input[type="checkbox"] + label:hover::before{
|
||||
border: 5px solid var(--cssPageWeaver-color-accent-hover);
|
||||
}
|
||||
|
||||
/* INPUT RADIO ---------------------------------- */
|
||||
|
||||
#cssPageWeaver_panel input[type="radio"] + label{
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
/* exception */
|
||||
/*#cssPageWeaver_panel #baseline-toggle + label::before{ display: none; }
|
||||
*/
|
||||
|
||||
/* BUTTONS-GROUP ------------------------------------------- */
|
||||
|
||||
#cssPageWeaver_panel .buttons-group button,
|
||||
#cssPageWeaver_panel .buttons-group .button{
|
||||
display: inline-block;
|
||||
--size: 40px;
|
||||
background-color: transparent;
|
||||
border: 1.5px solid var(--cssPageWeaver-color-accent);
|
||||
border-radius: var(--cssPageWeaver-radius);
|
||||
box-sizing: border-box;
|
||||
|
||||
color: var(--cssPageWeaver-color-accent);
|
||||
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .buttons-group .button:not(:has(*)){
|
||||
padding: .5em 0;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .buttons-group input{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .buttons-group button svg,
|
||||
#cssPageWeaver_panel .buttons-group .button svg{
|
||||
fill: var(--cssPageWeaver-color-accent);
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .buttons-group{
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 1ch;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .buttons-group.align-right{
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title:not(:empty) + .buttons-group{
|
||||
padding-top: 0.25em;
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .buttons-group .force-right{
|
||||
text-align: right;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#panel-buttons .buttons-group{
|
||||
padding-top: 1.25em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
#panel-buttons .buttons-group button,
|
||||
#panel-buttons .buttons-group .button{
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel input + label.label-block{
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel .panel-group-title.button-toggle label::before{ display: none!important; }
|
||||
|
||||
/* CHECKED BUTTONS --------------------------------------- */
|
||||
|
||||
#cssPageWeaver_panel .buttons-group input:checked + button,
|
||||
#cssPageWeaver_panel .buttons-group input:checked + .button{
|
||||
background-color: var(--cssPageWeaver-color-accent);
|
||||
border-color: var(--cssPageWeaver-color-accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
#cssPageWeaver_panel .buttons-group input:checked + button svg,
|
||||
#cssPageWeaver_panel .buttons-group input:checked + .button svg{
|
||||
fill: var(--cssPageWeaver-color-bg);
|
||||
}
|
||||
|
||||
/* TOGGLE LABEL ICONS --------------------------------------- */
|
||||
|
||||
#cssPageWeaver_panel input[id*="toggle"] + .buttons-group{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel input[id*="toggle"]:checked + .buttons-group .button:nth-of-type(1),
|
||||
#cssPageWeaver_panel input[id*="toggle"]:not(:checked) + .buttons-group .button:nth-of-type(2){
|
||||
background-color: var(--cssPageWeaver-color-accent);
|
||||
border-color: var(--cssPageWeaver-color-accent);
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel input[id*="toggle"]:checked + .buttons-group .button:nth-of-type(1) svg,
|
||||
#cssPageWeaver_panel input[id*="toggle"]:not(:checked) + .buttons-group .button:nth-of-type(2) svg{
|
||||
fill: var(--cssPageWeaver-color-bg);
|
||||
}
|
||||
|
||||
/* DETAILS CONTAINER< ------------------- */
|
||||
|
||||
[data-open-container*="details"] ~ [id*="details"]{
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
top: 1em;
|
||||
|
||||
max-height: 0px;
|
||||
overflow: hidden;
|
||||
transition: max-height 1s;
|
||||
}
|
||||
|
||||
[data-open-container*="details"]:checked ~ [id*="details"]{
|
||||
max-height: 100vh;
|
||||
margin-bottom: 0.5em;
|
||||
border-top: .5px solid var(--cssPageWeaver-color-border-light);
|
||||
}
|
||||
|
||||
|
||||
/* HOVER BUTTONS ------------------------------------------------------------ */
|
||||
|
||||
#cssPageWeaver_panel .buttons-group button:hover,
|
||||
#cssPageWeaver_panel .buttons-group .button:hover{
|
||||
background-color: var(--cssPageWeaver-color-accent-hover)!important;
|
||||
border-color: var(--cssPageWeaver-color-accent-hover)!important;
|
||||
}
|
||||
#cssPageWeaver_panel .buttons-group button:hover svg,
|
||||
#cssPageWeaver_panel .buttons-group .button:hover svg{
|
||||
fill: var(--cssPageWeaver-color-bg)!important;
|
||||
}
|
||||
|
||||
/* SHORTCUT ------------------------------------------------------------ */
|
||||
|
||||
#cssPageWeaver_panel .shortcut-list{
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: .25em;
|
||||
margin-top: .5em;
|
||||
}
|
||||
#cssPageWeaver_panel .shortcut-list li{
|
||||
font-size: .75em;
|
||||
color: var(--cssPageWeaver-color-text);
|
||||
border: 1px solid;
|
||||
border-radius: .25em;
|
||||
padding: .2em;
|
||||
cursor: help;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
/* List hidden hooks ------------------------------------------------------------ */
|
||||
|
||||
#cssPageWeaver_panel #hidden-features h1 {
|
||||
font-weight: bold;
|
||||
margin-bottom: .5em;
|
||||
|
||||
}
|
||||
#cssPageWeaver_panel #hidden-features summary {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding-top: 1em;
|
||||
font-size: .75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
#cssPageWeaver_panel #hidden-features ul{
|
||||
list-style: none;
|
||||
font-size: .75rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .75em;
|
||||
}
|
||||
#cssPageWeaver_panel #hidden-features ul li{
|
||||
margin-bottom: .25em;
|
||||
color: var(--cssPageWeaver-color-bg);
|
||||
padding: .2em .4em;
|
||||
border-radius: .2em;
|
||||
background: var(--cssPageWeaver-color-accent);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#cssPageWeaver_panel #hidden-features summary::marker {
|
||||
display: none;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,42 @@
|
|||
@font-face {
|
||||
src: url('IBMPlexMono-Regular.woff2') format("woff2"),
|
||||
url('IBMPlexMono-Regular.woff') format("woff");
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
src: url('IBMPlexMono-Italic.woff2') format("woff2"),
|
||||
url('IBMPlexMono-Italic.woff') format("woff");
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
src: url('IBMPlexMono-Medium.woff2') format("woff2"),
|
||||
url('IBMPlexMono-Medium.woff') format("woff");
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
src: url('IBMPlexMono-MediumItalic.woff2') format("woff2"),
|
||||
url('IBMPlexMono-MediumItalic.woff') format("woff");
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
}
|
||||
@font-face {
|
||||
src: url('IBMPlexMono-SemiBold.woff2') format("woff2"),
|
||||
url('IBMPlexMono-SemiBold.woff') format("woff");
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
src: url('IBMPlexMono-SemiBoldItalic.woff2') format("woff2"),
|
||||
url('IBMPlexMono-SemiBoldItalic.woff') format("woff");
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
}
|
||||
1
public/csspageweaver/lib/csstree.min.js
vendored
Normal file
1
public/csspageweaver/lib/csstree.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
33190
public/csspageweaver/lib/paged.esm.js
Normal file
33190
public/csspageweaver/lib/paged.esm.js
Normal file
File diff suppressed because it is too large
Load diff
127
public/csspageweaver/main.js
Normal file
127
public/csspageweaver/main.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* @author Benjamin G. <ecrire@bnjm.eu>, Julie Blanc <contact@julie-blanc.fr>
|
||||
* @tutorial https://gitlab.com/csspageweaver/csspageweaver/-/wikis/home
|
||||
* Credit: This global tool is based on an original idea from Julie Blanc
|
||||
*/
|
||||
|
||||
import CssPageWeaver_Dict from './modules/dict.js';
|
||||
import CssPageWeaver_GUI from './modules/gui.js';
|
||||
import CssPageWeaver_SimpleRender from './modules/render.js';
|
||||
import CssPageWeaver_FrameRender from './modules/frame_render.js';
|
||||
|
||||
import * as csstree from './lib/csstree.min.js';
|
||||
|
||||
/**
|
||||
* This script manage the way CSS Page Weaver
|
||||
* With or without GUI, with or without common dictionary
|
||||
* With simple render or framed render
|
||||
* Call instances on purpose
|
||||
*/
|
||||
|
||||
//👋 HERE - Edit method here or from you own script
|
||||
if(typeof cssPageWeaver_method == "undefined"){
|
||||
window.cssPageWeaver_method = {
|
||||
gui: true,
|
||||
dict: true,
|
||||
render: "simple",
|
||||
}
|
||||
}
|
||||
|
||||
// Dict
|
||||
if(cssPageWeaver_method.dict){
|
||||
window.cssPageWeaver_dict = new CssPageWeaver_Dict()
|
||||
}
|
||||
|
||||
// GUI
|
||||
if(cssPageWeaver_method.gui){
|
||||
const initGUI = () => {
|
||||
customElements.define('csspageweaver-gui', CssPageWeaver_GUI)
|
||||
window.cssPageWeaver_gui = document.createElement('csspageweaver-gui');
|
||||
cssPageWeaver_gui.classList.add("pagedjs-ignore");
|
||||
document.body.insertAdjacentElement('afterbegin', cssPageWeaver_gui)
|
||||
};
|
||||
|
||||
// Si le DOM est déjà chargé, initialiser immédiatement
|
||||
// Sinon, attendre l'événement DOMContentLoaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener("DOMContentLoaded", initGUI);
|
||||
} else {
|
||||
initGUI();
|
||||
}
|
||||
}
|
||||
|
||||
// SIMPLE RENDER
|
||||
if(cssPageWeaver_method.dict && cssPageWeaver_method.render == "simple"){
|
||||
// If pagedjs is defined, wait for the 'cssPageWeaver_dictInit' event to initialize
|
||||
document.addEventListener('cssPageWeaver-dictInit', () => {
|
||||
|
||||
window.cssPageWeaver_simpleRender = new CssPageWeaver_SimpleRender()
|
||||
|
||||
cssPageWeaver_simpleRender.interface = `${cssPageWeaver.directory.root}/interface/css/interface.css`
|
||||
|
||||
cssPageWeaver_simpleRender.hook = [
|
||||
...cssPageWeaver_dict.getFeaturesHookAsArray(),
|
||||
...cssPageWeaver.user?.hook
|
||||
]
|
||||
|
||||
cssPageWeaver_simpleRender.css = [
|
||||
...cssPageWeaver.user?.css,
|
||||
...cssPageWeaver_dict.getFeaturesStyleAsArray()
|
||||
]
|
||||
|
||||
cssPageWeaver_simpleRender.setup()
|
||||
});
|
||||
}
|
||||
|
||||
// FRAMED RENDER
|
||||
if(cssPageWeaver_method.dict && cssPageWeaver_method.render == "frame"){
|
||||
/* Using with shadow */
|
||||
document.addEventListener('cssPageWeaver-dictInit', () => {
|
||||
|
||||
customElements.define('csspageweaver-frame', CssPageWeaver_FrameRender);
|
||||
window.cssPageWeaver_frame = document.createElement('csspageweaver-frame');
|
||||
|
||||
cssPageWeaver_frame.interface = `${cssPageWeaver.directory.root}/interface/css/interface.css`
|
||||
|
||||
cssPageWeaver_frame.hook = [
|
||||
...cssPageWeaver_dict.getFeaturesHookAsArray(),
|
||||
...cssPageWeaver.user?.hook
|
||||
]
|
||||
|
||||
cssPageWeaver_frame.css = [
|
||||
...cssPageWeaver.user?.css,
|
||||
...cssPageWeaver.stylesheet.features
|
||||
]
|
||||
|
||||
document.body.insertAdjacentElement('afterbegin', cssPageWeaver_frame)
|
||||
|
||||
// Simulate Change
|
||||
/*
|
||||
setTimeout( async () => {
|
||||
cssPageWeaver_frame.css.push('/css/style-2.css')
|
||||
cssPageWeaver_frame.reloadDocument('/')
|
||||
}, 2000)
|
||||
*/
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/* Using without GUI or Dict */
|
||||
if( !cssPageWeaver_method.dict && !cssPageWeaver_method.dict){
|
||||
if(cssPageWeaver_method.render == "frame"){
|
||||
customElements.define('csspageweaver-frame', CssPageWeaver_FrameRender);
|
||||
window.cssPageWeaver_frame = document.createElement('csspageweaver-frame');
|
||||
cssPageWeaver_frame.interface = `csspageweaver/interface/css/interface.css`
|
||||
cssPageWeaver_frame.css = ['/css/style.css']
|
||||
document.body.insertAdjacentElement('afterbegin', cssPageWeaver_frame)
|
||||
} else {
|
||||
window.cssPageWeaver_simpleRender = new CssPageWeaver_SimpleRender()
|
||||
cssPageWeaver_simpleRender.interface = `csspageweaver/interface/css/interface.css`
|
||||
cssPageWeaver_simpleRender.hook = []
|
||||
cssPageWeaver_simpleRender.css.push('/css/style.css')
|
||||
cssPageWeaver_simpleRender.setup()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
37
public/csspageweaver/manifest.json
Normal file
37
public/csspageweaver/manifest.json
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"plugins": [
|
||||
"frenchTypoRegex",
|
||||
"baseline",
|
||||
"grid",
|
||||
"moveElems",
|
||||
"marginBox",
|
||||
"imposition",
|
||||
"inlineNotes",
|
||||
"footnotesFix",
|
||||
"spread",
|
||||
"previewPage",
|
||||
"reloadInPlace",
|
||||
"fullPage",
|
||||
"floatPage"
|
||||
],
|
||||
"pluginsParameters": {
|
||||
"inlineNotes": {
|
||||
"input": ".footnote-ref",
|
||||
"containerNotes": ".footnotes",
|
||||
"newClass": "footnote"
|
||||
},
|
||||
"footnotesFix": {
|
||||
"selector": ".footnote",
|
||||
"reset": ".chapter, .carnet"
|
||||
}
|
||||
},
|
||||
"css": [
|
||||
"/style-print.css"
|
||||
],
|
||||
"hook": [
|
||||
"/js/carnet.js",
|
||||
"/js/beforeParsed.js",
|
||||
"/js/afterLayout.js"
|
||||
]
|
||||
}
|
||||
|
||||
333
public/csspageweaver/modules/dict.js
Normal file
333
public/csspageweaver/modules/dict.js
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
/**
|
||||
* @classdesc This provides a common dictionnary for a Paged.js tools.
|
||||
*
|
||||
* Web Component will fetch the list of features. For each one a class will be instantiated.
|
||||
* This class groups all the key data. From this list of instances
|
||||
* the component will refine the data, import all necessary scripts, hooks and stylesheet.
|
||||
*
|
||||
* This very logic can be observed at the end of the component in the setup() function.
|
||||
*
|
||||
* @author Benjamin G. <ecrire@bnjm.eu>
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @tutorial https://gitlab.com/csspageweaver/csspageweaver/-/wikis/home
|
||||
*/
|
||||
|
||||
class CssPageWeaver_Dict {
|
||||
constructor(){
|
||||
console.log('CSS Page Weaver Dict initialized');
|
||||
|
||||
// Define a Feature class to manage individual features
|
||||
this.Feature = class {
|
||||
constructor(featureConfig, id, directory, config) {
|
||||
this.id = id
|
||||
this.directory = directory
|
||||
this.ui = featureConfig.ui
|
||||
this.parameters = featureConfig.parameters
|
||||
this.hook = featureConfig.hook
|
||||
this.script = featureConfig.script
|
||||
this.stylesheet = featureConfig.stylesheet
|
||||
this.globalParameters = config
|
||||
}
|
||||
}
|
||||
|
||||
this.setup()
|
||||
}
|
||||
|
||||
/*-- Generics functions --*/
|
||||
|
||||
/**
|
||||
* Clear and split feature directory path to define an unique ID
|
||||
* @param {string} path - Feature folder path.
|
||||
* @returns {string} - Feature ID.
|
||||
*/
|
||||
getIdFromPath(path){
|
||||
return path.trim().split("/").filter(segment => segment.trim() !== '').pop()
|
||||
}
|
||||
|
||||
/*-- Dict --*/
|
||||
|
||||
setSharedDictionnary(){
|
||||
|
||||
window.cssPageWeaver = {}
|
||||
// Get the document title and remove spaces for use as a variable
|
||||
cssPageWeaver.docTitle = document.getElementsByTagName("title")[0].text.replace(/ /g, "");
|
||||
|
||||
// Set path & directory
|
||||
cssPageWeaver.directory = {}
|
||||
cssPageWeaver.directory.root = `${window.location.origin}/csspageweaver`
|
||||
cssPageWeaver.directory.plugins = `${cssPageWeaver.directory.root}/plugins`
|
||||
|
||||
// Initialise user custom files
|
||||
cssPageWeaver.user = {}
|
||||
|
||||
// Object to hold all features
|
||||
cssPageWeaver.features = {}
|
||||
}
|
||||
|
||||
/*-- Import or list files --*/
|
||||
|
||||
/**
|
||||
* Import the list of feature names from the manifest file or directory.
|
||||
*
|
||||
* This method attempts to import the feature names from a manifest.json file.
|
||||
* If the manifest is not found, it falls back to listing the feature names
|
||||
* from the plugin directory.
|
||||
*
|
||||
* @returns {Promise<Array>} A promise that resolves to an array of feature names.
|
||||
*/
|
||||
async importManifest(){
|
||||
|
||||
try{
|
||||
// Attempt to read the manifest file to get the list of features
|
||||
return await this.importJson(`${cssPageWeaver.directory.root}/`,`manifest.json`);
|
||||
} catch(error){
|
||||
console.log('Manifest not found, trying alternative method.');
|
||||
// If the manifest is not found, list feature names from the plugin directory
|
||||
let featuresNamesList = await this.listDir(cssPageWeaver.directory.plugins);
|
||||
// Return object
|
||||
return {"plugins": featuresNamesList }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports an HTML template file from a given directory and file name.
|
||||
* @param {string} dir - The directory where the template file is located.
|
||||
* @param {string} file - The name of the template file.
|
||||
* @returns {Promise<string>} - A promise that resolves to the contents of the template file.
|
||||
*/
|
||||
async importTemplate(dir, file) {
|
||||
const response = await fetch(`${dir}/${file}`);
|
||||
const template = await response.text();
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a Javascript file from a specified directory.
|
||||
*
|
||||
* @param {string} path - The path path where the JS file is located.
|
||||
* @returns {Promise<Object>} A promise that resolves to the JS content of the file.
|
||||
* @throws {Error} Throws an error if the fetch request fails or if the file is not found.
|
||||
*/
|
||||
async importJs(path) {
|
||||
try {
|
||||
return await import(path)
|
||||
} catch (error) {
|
||||
console.error(`Error loading JS for ${path}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a JSON file from a specified directory.
|
||||
*
|
||||
* @param {string} dir - The directory path where the JSON file is located.
|
||||
* @param {string} file - The name of the JSON file to import.
|
||||
* @returns {Promise<Object>} A promise that resolves to the JSON content of the file.
|
||||
* @throws {Error} Throws an error if the fetch request fails or if the file is not found.
|
||||
*/
|
||||
async importJson(dir, file) {
|
||||
|
||||
try {
|
||||
const response = await fetch(`${dir}/${file}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`🚨 Oups. Can't find ${file} in ${this.getIdFromPath(dir)}`);
|
||||
}
|
||||
const json = await response.json();
|
||||
return json;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching json file in ${this.getIdFromPath(dir)}:`, error);
|
||||
throw error; // Re-throw the error to be caught in importJson
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the directories within a specified directory by fetching and parsing its HTML content.
|
||||
*
|
||||
* This method fetches the automatically generated HTML page listing content of a directory,
|
||||
* parses it to find links, and extracts directory names based on a date pattern.
|
||||
*
|
||||
* Server must allow listing directory.
|
||||
* Yes. It's dirty.
|
||||
*
|
||||
* @param {string} dir - The URL of the directory to list.
|
||||
* @returns {Promise<Array<string>>} A promise that resolves to an array of directory names.
|
||||
* @throws {Error} Throws an error if the fetch request fails or if there is an issue parsing the content.
|
||||
*/
|
||||
async listDir(dir) {
|
||||
try {
|
||||
const response = await fetch(dir);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch plugin directory'); }
|
||||
const text = await response.text();
|
||||
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(text, 'text/html');
|
||||
const links = doc.querySelectorAll('a');
|
||||
|
||||
const directories = [];
|
||||
const datePattern = /\d+\//; // Matches the first number followed by a '/'
|
||||
|
||||
links.forEach(link => {
|
||||
const name = link.textContent;
|
||||
if (name) {
|
||||
const match = name.match(datePattern);
|
||||
if (match) {
|
||||
// Remove everything from the date part onward
|
||||
const cleanedName = name.split(datePattern)[0].trim();
|
||||
directories.push(cleanedName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return directories;
|
||||
} catch (error) {
|
||||
console.error(`Error listing ${dir} items`, error);
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*-- CSS --*/
|
||||
|
||||
/**
|
||||
* Compare declared and DOM loaded stylesheet. Warn user if he missed one.
|
||||
* {array} - An array of stylesheet declared by user in manifest
|
||||
*/
|
||||
lookForForgottenStylesheet(userStylesheets) {
|
||||
// Get all stylesheet (not for screen) already loaded
|
||||
const links = document.querySelectorAll(`link[rel="stylesheet"]:not([media="screen"]`)
|
||||
|
||||
let domStylesheets = []
|
||||
|
||||
// Iterate over each link element
|
||||
links.forEach(link => {
|
||||
// Exlude CSS Page Weaver stylesheet
|
||||
if(!link.href.includes('csspageweaver')){
|
||||
domStylesheets.push(link.href.split('?')[0])
|
||||
}
|
||||
});
|
||||
|
||||
// We'll not retrieve already loaded CSS just warn user if he forget one stylesheet
|
||||
//return CSSArray
|
||||
|
||||
let missedStylesheets = domStylesheets.filter(sheet => !userStylesheets.includes(sheet));
|
||||
|
||||
if(missedStylesheets.length > 0){
|
||||
console.warn(`😶🌫️ Did you missed to include ${missedStylesheets.join(', ') } into csspageweaver/manifest.json? `)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*-- Features --*/
|
||||
|
||||
/**
|
||||
* Initializes features by loading their configurations and creating instances.
|
||||
*
|
||||
* This method reads feature names from a manifest, imports their configurations,
|
||||
* and sets up their UI, hooks and scripts if available.
|
||||
*/
|
||||
async initializeFeatures(featuresNamesList, featuresParameters) {
|
||||
|
||||
// Loop all features
|
||||
for (const featureName of featuresNamesList) {
|
||||
const featureDir = `${cssPageWeaver.directory.plugins}/${featureName}/`
|
||||
|
||||
const featureEmbedConfig = await this.importJson(featureDir, `config.json`)
|
||||
const featureManifestConfig = {"parameters" : featuresParameters[featureName]} || {}
|
||||
const featureConfig = {...featureEmbedConfig, ...featureManifestConfig}
|
||||
|
||||
// Create a new instance of the feature
|
||||
let feature = new this.Feature(featureConfig, featureName, featureDir, cssPageWeaver.parameters);
|
||||
|
||||
// Import feature's HTML template if specified
|
||||
if(feature.ui){
|
||||
if(feature.ui.template){
|
||||
feature.ui.html = await this.importTemplate(featureDir, feature.ui.template);
|
||||
}
|
||||
}
|
||||
|
||||
// Import the feature's script and hook if specified
|
||||
['script', 'hook'].forEach(property => {
|
||||
if (feature[property]) {
|
||||
let fileName = feature[property];
|
||||
feature[property] = this.importJs(`${featureDir}/${fileName}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Store the initialized feature
|
||||
cssPageWeaver.features[featureName] = feature
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async initializeUserHook(handlerList = []) {
|
||||
cssPageWeaver.user.hook = await Promise.all(
|
||||
handlerList.map(async path => {
|
||||
return await this.importJs(path) // Return the promise from importJs
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves features that have a hook from the `cssPageWeaver` object.
|
||||
* @returns {Array} An array of features that have a hook. Returns an empty array if `cssPageWeaver` is undefined or does not have features.
|
||||
*/
|
||||
getFeaturesHookAsArray(){
|
||||
// Convert the features object to an array
|
||||
const featuresArray = Object.values(cssPageWeaver.features);
|
||||
// Filter to only features with a hook
|
||||
return featuresArray
|
||||
.filter(feature => feature.hook)
|
||||
.map(feature => feature.hook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates the CSS paths from all features that have a stylesheet.
|
||||
* @returns {Array} An array of CSS file paths.
|
||||
*/
|
||||
getFeaturesStyleAsArray(){
|
||||
// Convert the features object to an array
|
||||
const featuresArray = Object.values(cssPageWeaver.features);
|
||||
// Filter to only features with a hook
|
||||
return featuresArray
|
||||
.filter(feature => feature.stylesheet)
|
||||
.map(feature => `${feature.directory}${feature.stylesheet}`);
|
||||
}
|
||||
|
||||
/*-- Setup --*/
|
||||
|
||||
async setup(){
|
||||
|
||||
// Set Shared dict
|
||||
this.setSharedDictionnary()
|
||||
|
||||
// Import Manifest
|
||||
let manifest = await this.importManifest()
|
||||
|
||||
// Get features list as an array of class for each
|
||||
await this.initializeFeatures(manifest.plugins, manifest.pluginsParameters)
|
||||
|
||||
// list user stylesheets
|
||||
cssPageWeaver.user.css = manifest.css || []
|
||||
|
||||
// list stylesheets as convenient object
|
||||
cssPageWeaver.stylesheet = {
|
||||
features: this.getFeaturesStyleAsArray(),
|
||||
user: cssPageWeaver.user.css
|
||||
}
|
||||
|
||||
// Warn user if he missed a stylesheet
|
||||
this.lookForForgottenStylesheet(cssPageWeaver.user.css)
|
||||
|
||||
// Import User handlers
|
||||
await this.initializeUserHook(manifest.hook)
|
||||
|
||||
// Dispatch an event to signal that features are loaded
|
||||
const event = new Event('cssPageWeaver-dictInit');
|
||||
document.dispatchEvent(event)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CssPageWeaver_Dict
|
||||
410
public/csspageweaver/modules/frame_render.js
Normal file
410
public/csspageweaver/modules/frame_render.js
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
/**
|
||||
* @classdsec Render paged through a shadow element and return desired pages
|
||||
* @author Benjamin G. <ecrire@bnjm.eu>
|
||||
* @tutorial https://gitlab.com/csspageweaver/csspageweaver/-/wikis/home
|
||||
* Credit: This code is based on an original idea from Julien Taquet
|
||||
*/
|
||||
|
||||
import { Handler, Previewer } from '../lib/paged.esm.js';
|
||||
import CssPageWeaver_PreRender from './pre_render.js';
|
||||
|
||||
// Define a custom web component
|
||||
class CssPageWeaver_FrameRender extends HTMLElement {
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
// Attach a shadow root to the element
|
||||
this.attachShadow({ mode: 'open' });
|
||||
|
||||
this.range = {
|
||||
from: 0,
|
||||
to: Infinity
|
||||
}
|
||||
|
||||
this.container = {
|
||||
origin: {
|
||||
body: document.body,
|
||||
head: document.head
|
||||
},
|
||||
pages: null
|
||||
}
|
||||
|
||||
this.hook = []
|
||||
this.css = []
|
||||
|
||||
console.log('CSS Page Weaver Frame Render initialized');
|
||||
|
||||
}
|
||||
|
||||
/*-- CSS --*/
|
||||
|
||||
hideFrame(){
|
||||
this.setAttribute("style", "position: absolute; left: 100vw; max-height: 0; width: 100vw; overflow: hidden;")
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the dimensions from the document body and applies them to the container inside the shadow DOM.
|
||||
* This method ensures that the shadow DOM container matches the size and position of the document body.
|
||||
*/
|
||||
copyDimensionsFromBody() {
|
||||
// Get the offsetHeight and client rect of the body.
|
||||
const offsetHeight = this.container.origin.body.offsetHeight;
|
||||
const clientRect = this.container.origin.body.getBoundingClientRect();
|
||||
|
||||
// Set the dimensions on the container inside the shadow DOM.
|
||||
this.container.shadow.body.style.width = `${clientRect.width}px`;
|
||||
this.container.shadow.body.style.height = `${clientRect.height}px`;
|
||||
this.container.shadow.body.style.top = `${clientRect.top}px`;
|
||||
this.container.shadow.body.style.left = `${clientRect.left}px`;
|
||||
|
||||
// Optionally, log the dimensions for debugging.
|
||||
console.log('Body Dimensions:', { offsetHeight, clientRect });
|
||||
console.log('Container Dimensions:', {
|
||||
height: this.container.shadow.body.style.height,
|
||||
width: this.container.shadow.body.style.width
|
||||
})
|
||||
}
|
||||
|
||||
/*-- DOM --*/
|
||||
|
||||
/**
|
||||
* Resets the shadow root by clearing its existing content and creating a new HTML structure.
|
||||
* This method ensures that the shadow root is empty and ready for new content.
|
||||
*/
|
||||
resetShadowRoot() {
|
||||
// Clear existing content in the shadow root
|
||||
while (this.shadowRoot.firstChild) {
|
||||
this.shadowRoot.removeChild(this.shadowRoot.firstChild);
|
||||
}
|
||||
|
||||
// Create new HTML structure
|
||||
const head = document.createElement('head');
|
||||
const body = document.createElement('body');
|
||||
|
||||
// Append head and body to the shadow root
|
||||
this.shadowRoot.appendChild(head);
|
||||
this.shadowRoot.appendChild(body);
|
||||
|
||||
this.container.shadow = { head, body };
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes elements to the light DOM, handling both individual elements and arrays of elements.
|
||||
*
|
||||
* @param {Element|Element[]} element - The element or array of elements to pass to the light DOM.
|
||||
* @returns {void}
|
||||
*/
|
||||
passElementsToLightDom(element) {
|
||||
|
||||
// Array means a slection of pages had been made
|
||||
|
||||
if (Array.isArray(element)) {
|
||||
|
||||
|
||||
element.forEach(page => {
|
||||
if (page.getAttribute('data-page-number')) {
|
||||
let page_clone = page.cloneNode(true)
|
||||
let page_number = parseInt(page.getAttribute('data-page-number'));
|
||||
let page_ref = this.container.origin.body.querySelector(`[data-page-number="${page_number}"]`);
|
||||
|
||||
page_ref?.remove();
|
||||
|
||||
let previous = this.container.origin.pages.querySelector(`[data-page-number="${page_number - 1}"]`)
|
||||
previous.insertAdjacentElement('afterend', page_clone);
|
||||
|
||||
// TODO Check if there are pages to remove
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (element.classList.contains('pagedjs_pages')) {
|
||||
this.container.origin.body.querySelector('.pagedjs_pages')?.remove();
|
||||
this.container.origin.body.appendChild(element);
|
||||
} else {
|
||||
let el_UI = parseInt(element.getAttribute('data-unique-identifier'));
|
||||
let elementOriginal = this.container.origin.body.querySelector(`[data-unique-identifier="${el_UI}"]`);
|
||||
|
||||
elementOriginal?.replaceWith(element);
|
||||
|
||||
// TODO Need to check if break token is still coherent.
|
||||
}
|
||||
}
|
||||
|
||||
// Update pages container
|
||||
this.container.origin.pages = this.container.origin.body.querySelector('.pagedjs_pages')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique CSS selector for a given HTML element.
|
||||
* This method attempts to create a selector that uniquely identifies the element
|
||||
* based on its attributes and position in the DOM hierarchy.
|
||||
*
|
||||
* @param {HTMLElement} element - The HTML element for which to generate a unique selector.
|
||||
* @returns {string} - A CSS selector string that uniquely identifies the element.
|
||||
*/
|
||||
getUniqueSelector(element) {
|
||||
|
||||
if(element.tagName == 'BODY'){
|
||||
return 'body'
|
||||
}
|
||||
|
||||
if (element.getAttribute('data-unique-identifier')) {
|
||||
// If the element has data-unique-identifier, use it
|
||||
return `[data-unique-identidier='${element.getAttribute('data-unique-identifier')}]`;
|
||||
}
|
||||
|
||||
if (element.id) {
|
||||
// If the element has an ID, use it
|
||||
return `#${element.id}`;
|
||||
}
|
||||
|
||||
if (element.className && element.className.trim() !== '') {
|
||||
// If the element has a class, use it
|
||||
// Note: This might not be unique if other elements share the same class
|
||||
return `.${element.className.split(' ').join('.')}`;
|
||||
}
|
||||
|
||||
// Fallback to using the tag name and its position in the hierarchy
|
||||
let selector = element.tagName.toLowerCase();
|
||||
let parent = element.parentElement;
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
/* Import & reload */
|
||||
|
||||
/**
|
||||
* Fetches a document from the specified path.
|
||||
*
|
||||
* @param {string} path - The URL or path to the document to be fetched.
|
||||
* @returns {Promise<Document|Object>} - A promise that resolves to the fetched HTML document or JSON object.
|
||||
* @throws {Error} - Throws an error if the network response is not ok or if there is a problem with the fetch operation.
|
||||
*/
|
||||
async fetchDocument(path) {
|
||||
try {
|
||||
const response = await fetch(path);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
|
||||
if (path.endsWith('.html') || path.endsWith('/')) {
|
||||
const text = await response.text();
|
||||
const parser = new DOMParser();
|
||||
|
||||
// Parse the HTML content into a Document object
|
||||
const html = parser.parseFromString(text, "text/html");
|
||||
|
||||
// Get all script elements
|
||||
//const scripts = html.querySelectorAll('script');
|
||||
|
||||
// Remove each script element
|
||||
//scripts.forEach(script => script.remove());
|
||||
|
||||
return html;
|
||||
} else if (path.endsWith('.json')) {
|
||||
// Parse the JSON response
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('There was a problem with the fetch operation:', error);
|
||||
throw error; // Rethrow the error to handle it outside if needed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads a document from the specified path and updates the instance content.
|
||||
*
|
||||
* @param {string} path - The URL or path to the document to be reloaded.
|
||||
* @returns {Promise<void>} - A promise that resolves when the document is reloaded and the content is updated.
|
||||
*/
|
||||
async reloadDocument(path) {
|
||||
let newDoc = await this.fetchDocument(path);
|
||||
|
||||
let selector = this.getUniqueSelector(this.container.origin.body);
|
||||
|
||||
let newContainer = await newDoc.querySelector(selector);
|
||||
|
||||
this._render.setUniqueIdentfier(newContainer);
|
||||
|
||||
// DEBUG
|
||||
const elements = newContainer.querySelectorAll('p');
|
||||
elements.forEach(el => {
|
||||
el.textContent = el.textContent.replaceAll('e', '🙈');
|
||||
});
|
||||
|
||||
// Set instance content with new content
|
||||
this.content = this._render.storeElements(newContainer, true);
|
||||
|
||||
// Show me!
|
||||
this.setView(false);
|
||||
}
|
||||
|
||||
/* Paged JS */
|
||||
|
||||
/**
|
||||
* Determines which elements or pages within a document should be passed to the light DOM.
|
||||
* Handles different scenarios for selecting elements or pages to pass, including specific
|
||||
* elements, all pages, or a range of pages.
|
||||
*/
|
||||
defineElementsToPass() {
|
||||
|
||||
let toPass;
|
||||
|
||||
// Detect if we keep a page or an element
|
||||
if (this.range.element) {
|
||||
// Get element to pass
|
||||
toPass = this.container.pages.querySelector(`[data-unique-identifier="${this.range.element}"]`);
|
||||
// Append element to light DOM
|
||||
|
||||
// Finally, delete the element property from the range
|
||||
delete this.range.element;
|
||||
console.log(`🔃 Update page ${this.range.to}`)
|
||||
|
||||
} else if (this.range.from === 0 && this.range.to === Infinity) {
|
||||
// Just clone pagedjs_pages container
|
||||
toPass = this.container.pages.cloneNode(true);
|
||||
|
||||
if(!this.firstTime){
|
||||
console.log(`🔃 Update full document `)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Array of pages to pass
|
||||
toPass = [];
|
||||
|
||||
// Select all pages with the data-page-number attribute within the clone
|
||||
const pages = this.container.pages.querySelectorAll('[data-page-number]');
|
||||
|
||||
// Iterate through the pages and remove those outside the range
|
||||
pages.forEach(page => {
|
||||
const pageNumber = parseInt(page.getAttribute('data-page-number'));
|
||||
if (pageNumber >= this.range.from && pageNumber <= this.range.to) {
|
||||
toPass.push(page.cloneNode(true));
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`🔃 Update ${toPass.length} page${toPass.length > 1 ? 's' : ''}, from page ${this.range.from}`)
|
||||
}
|
||||
|
||||
this.passElementsToLightDom(toPass);
|
||||
}
|
||||
/**
|
||||
* Pagedjs library append style on document head.
|
||||
* Here we edit this very fucntion to append styles on head's shadow container
|
||||
* Without this, page break is erratic. Pages are missing.
|
||||
*/
|
||||
redefineInsertFunction(){
|
||||
const polisherInstance = this.previewer.polisher;
|
||||
|
||||
// Define the new insert function
|
||||
polisherInstance.insert = function(text) {
|
||||
let style = document.createElement("style");
|
||||
style.setAttribute("data-css-page-weaver-inserted-styles", "true");
|
||||
style.appendChild(document.createTextNode(text));
|
||||
cssPageWeaver_frame.container.shadow.head.appendChild(style);
|
||||
this.inserted.push(style);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends Paged.js styles to the shadow DOM.
|
||||
* This method creates a custom handler that clones styles from the document's head
|
||||
* to the shadow DOM's head before the content is parsed.
|
||||
* Without this, page break is erratic. Pages are missing.
|
||||
*/
|
||||
appendPagedJsStyle(){
|
||||
|
||||
class appendPagedStyleToShadow extends Handler {
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
}
|
||||
|
||||
beforeParsed(content){
|
||||
cssPageWeaver_frame._render.cloneElements('style[data-css-page-weaver-inserted-styles="true"]', cssPageWeaver_frame.container.shadow.head, document.head)
|
||||
|
||||
}
|
||||
}
|
||||
this.hook.push({default: appendPagedStyleToShadow})
|
||||
|
||||
}
|
||||
|
||||
/* Setup */
|
||||
|
||||
connectedCallback() {
|
||||
// Hide the entire frame
|
||||
this.hideFrame();
|
||||
|
||||
// Initialize a new instance of CssPageWeaver_PreRender
|
||||
this._render = new CssPageWeaver_PreRender();
|
||||
|
||||
// Append a style link to the document's head using the interface
|
||||
if (typeof this.interface === 'string' && this.interface.length > 0) {
|
||||
this._render.appendStyleLink(this.interface, this.container.origin.head);
|
||||
}
|
||||
|
||||
// Store elements from the renderer's container origin into this.content
|
||||
this.content = this._render.storeElements(this._render.container.origin, false);
|
||||
|
||||
// Set a unique identifier for the stored content
|
||||
this._render.setUniqueIdentfier(this.content);
|
||||
|
||||
// Copy styles appended by Paged.js to the document's head into the shadow DOM's head
|
||||
this.appendPagedJsStyle();
|
||||
|
||||
// Set up the view for rendering
|
||||
this.setView(true);
|
||||
}
|
||||
|
||||
async setView(firstTime) {
|
||||
// Initialize a new instance of Previewer
|
||||
this.previewer = new Previewer();
|
||||
|
||||
// TODO: This should register hook on fresh instance. It dont and duplicate hook. So I set a trick here.
|
||||
if(firstTime){
|
||||
// Register all hooks with the renderer
|
||||
await this._render.registerAllHook(this.hook, this.previewer);
|
||||
}
|
||||
|
||||
// Modify the Paged.js polisher insert function to append styles to the shadow DOM
|
||||
this.redefineInsertFunction();
|
||||
|
||||
// Clear the shadow root
|
||||
this.resetShadowRoot();
|
||||
|
||||
// Append style links to both the document's head and the shadow DOM's head if interface is a non-empty string
|
||||
if (typeof this.interface === 'string' && this.interface.length > 0) {
|
||||
this._render.appendStyleLink(this.interface, this.container.origin.head);
|
||||
this._render.appendStyleLink(this.interface, this.container.shadow.head);
|
||||
}
|
||||
|
||||
try {
|
||||
// Preview the content and log the number of rendered pages
|
||||
const pages = await this.previewer.preview(
|
||||
this.content,
|
||||
this.css,
|
||||
this.container.shadow.body
|
||||
);
|
||||
|
||||
this.container.pages = this.shadowRoot.querySelector('.pagedjs_pages');
|
||||
console.log('✅ Rendered', pages.total, 'pages');
|
||||
} catch (error) {
|
||||
// Handle any errors that occur during the preview operation
|
||||
console.error('Error during pagination:', error);
|
||||
}
|
||||
|
||||
// Update stylesheet by removing and cloning elements with inserted styles
|
||||
let styles = 'style[data-css-page-weaver-inserted-styles="true"]';
|
||||
this._render.removeElements(styles, this.container.origin.head);
|
||||
this._render.cloneElements(styles, this.container.shadow.head, this.container.origin.head);
|
||||
|
||||
// Define elements to pass
|
||||
this.defineElementsToPass();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CssPageWeaver_FrameRender
|
||||
|
||||
510
public/csspageweaver/modules/gui.js
Normal file
510
public/csspageweaver/modules/gui.js
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
/**
|
||||
* @classdesc A web component that provides a GUI for a Paged.js previewer.
|
||||
* Web Component will compose (or import) a HTML template and attach event listener
|
||||
* to allow front-end interactions.
|
||||
* @extends HTMLElement
|
||||
* @author Benjamin G. <ecrire@bnjm.eu>, Julie Blanc <contact@julie-blanc.fr>
|
||||
* @tutorial https://gitlab.com/csspageweaver/csspageweaver/-/wikis/home
|
||||
* Credit: This code is based on an original idea from Julie Blanc
|
||||
*/
|
||||
|
||||
class CssPageWeaver_GUI extends HTMLElement {
|
||||
|
||||
constructor (){
|
||||
super()
|
||||
console.log('CSS Page Weaver GUI Component initialized');
|
||||
}
|
||||
|
||||
/*-- Dict --*/
|
||||
|
||||
extendSharedDict(){
|
||||
cssPageWeaver.directory.interface = `${cssPageWeaver.directory.root}/interface`
|
||||
|
||||
// Initialize UI elements
|
||||
cssPageWeaver.ui = {}
|
||||
cssPageWeaver.ui.body = document.querySelector('body')
|
||||
cssPageWeaver.ui.shortcut_index = [] // For user convenience, store all keyboard shortcut
|
||||
|
||||
// Initialize parameters and events
|
||||
cssPageWeaver.ui.event = {}
|
||||
|
||||
// Set API
|
||||
cssPageWeaver.helpers = {
|
||||
addKeydownListener: this.addKeydownListener.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
/*-- CSS --*/
|
||||
|
||||
/**
|
||||
* Loads a CSS file and appends it to the document head.
|
||||
* @param {string} path - The path to the CSS file.
|
||||
*/
|
||||
appendCSStoDOM(path) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = path;
|
||||
link.setAttribute('data-css-page-weaver-gui', true)
|
||||
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Aggregates the CSS paths from all features that have a stylesheet.
|
||||
* @returns {Array} An array of CSS file paths.
|
||||
*/
|
||||
getFeaturesStyleAsArray(){
|
||||
// Convert the features object to an array
|
||||
const featuresArray = Object.values(cssPageWeaver.features);
|
||||
// Filter to only features with a hook
|
||||
return featuresArray
|
||||
.filter(feature => feature.stylesheet)
|
||||
.map(feature => `${feature.directory}${feature.stylesheet}`);
|
||||
}
|
||||
|
||||
/*-- DOM --*/
|
||||
|
||||
/**
|
||||
* Creates a panel for a feature with the specified UI configuration.
|
||||
*
|
||||
* This method constructs a form element for a feature, including a title,
|
||||
* description, toggle switch, and additional HTML content if specified.
|
||||
* It also handles shortcuts for the feature.
|
||||
*
|
||||
* @param {string} id - The ID of the feature.
|
||||
* @param {Object} ui - The UI configuration object for the feature.
|
||||
* @returns {HTMLElement} The created form element representing the feature's panel.
|
||||
*/
|
||||
createPanel(id, ui){
|
||||
function createTitle(){
|
||||
if(ui.title){
|
||||
// Create a title for this feature
|
||||
const title = document.createElement('h1');
|
||||
title.textContent = ui.title;
|
||||
|
||||
if(ui.description){
|
||||
title.title = ui.description
|
||||
}
|
||||
|
||||
// Compose title container
|
||||
titleContainer.appendChild(title);
|
||||
}
|
||||
}
|
||||
|
||||
function createDescription(){
|
||||
|
||||
if(ui.description){
|
||||
// Create a details element for the description
|
||||
const details = document.createElement('details');
|
||||
const summary = document.createElement('summary');
|
||||
summary.textContent = '?'
|
||||
const p = document.createElement('p')
|
||||
p.textContent = ui.description
|
||||
|
||||
// compose details container
|
||||
details.appendChild(summary)
|
||||
details.appendChild(p)
|
||||
|
||||
// Append the details container to the title container
|
||||
titleContainer.appendChild(details)
|
||||
}
|
||||
}
|
||||
|
||||
function createToggle(){
|
||||
//
|
||||
// If feature require a simple ON/OFF toggle,
|
||||
if(ui.toggle){
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.id = `${id}-toggle`;
|
||||
checkbox.name = `${id}-toggle`;
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.htmlFor = `${id}-toggle`;
|
||||
label.id = `label-${id}-toggle`;
|
||||
|
||||
const seeSpan = document.createElement('span');
|
||||
seeSpan.className = 'button-see button-not-selected';
|
||||
seeSpan.textContent = 'see';
|
||||
|
||||
const hideSpan = document.createElement('span');
|
||||
hideSpan.className = 'button-hide';
|
||||
hideSpan.textContent = 'hide';
|
||||
|
||||
label.appendChild(seeSpan);
|
||||
label.insertAdjacentHTML('beforeEnd', ' '); // This little hack to preserve harmony with handmade template
|
||||
label.appendChild(hideSpan);
|
||||
|
||||
// Append input & label to group-title
|
||||
titleContainer.appendChild(checkbox);
|
||||
titleContainer.appendChild(label);
|
||||
|
||||
form.classList.add('button-toggle')
|
||||
|
||||
// Add toggle to API
|
||||
cssPageWeaver.ui[id] = {
|
||||
toggleInput: checkbox,
|
||||
toggleLabel: label
|
||||
}
|
||||
cssPageWeaver.ui.event[id] = {
|
||||
toggleState: checkbox.checked
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function displayShortcut(){
|
||||
if(ui.shortcut){
|
||||
|
||||
// Function to convert key array to a human-readable string
|
||||
function keysToString(keyArray) {
|
||||
const keyMapping = {
|
||||
"shiftKey": "Shift",
|
||||
"ctrlKey": "Ctrl",
|
||||
"altKey": "Alt",
|
||||
"metaKey": "Meta"
|
||||
};
|
||||
|
||||
// Transform the array to a string like 'Ctrl + Z'
|
||||
const humanReadableKeys = keyArray.map(key => keyMapping[key] || key);
|
||||
return humanReadableKeys.join(' + ');
|
||||
}
|
||||
|
||||
const shortcutList = document.createElement('ul')
|
||||
shortcutList.className = "shortcut-list"
|
||||
|
||||
ui.shortcut.forEach(item => {
|
||||
|
||||
// If item disable shortcut display on panel
|
||||
if(item.tutorial == false){
|
||||
return
|
||||
}
|
||||
|
||||
// Create a list item for the shortcut
|
||||
const li = document.createElement('li')
|
||||
li.textContent = Array.isArray(item.keys) ? keysToString(item.keys) : item.keys
|
||||
li.title = item.description;
|
||||
|
||||
// Append the item to the shortcut list
|
||||
shortcutList.appendChild(li)
|
||||
})
|
||||
|
||||
// Append Shortcut list to its feature panel
|
||||
form.appendChild(shortcutList)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a form for this feature
|
||||
const form = document.createElement('form');
|
||||
form.className = `panel-group`;
|
||||
form.id = `${id}-form`;
|
||||
|
||||
// Create Title Container for this feature
|
||||
const titleContainer = document.createElement('div');
|
||||
titleContainer.className = 'panel-group-title';
|
||||
|
||||
createTitle()
|
||||
createDescription()
|
||||
createToggle()
|
||||
|
||||
// Append title group to form
|
||||
form.appendChild(titleContainer);
|
||||
|
||||
|
||||
// Append additional template to form
|
||||
if(ui.html){
|
||||
form.insertAdjacentHTML("beforeEnd", ui.html)
|
||||
}
|
||||
|
||||
displayShortcut()
|
||||
|
||||
// Return complete feature panel
|
||||
return form
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the panel container and subpanels for each feature.
|
||||
*
|
||||
* This method constructs a panel container and populates it with subpanels
|
||||
* for features that have a UI. It also lists features without a UI in a
|
||||
* separate section.
|
||||
*/
|
||||
createMainPanel() {
|
||||
// Create Panel container
|
||||
const formContainer = document.createElement('div');
|
||||
formContainer.id = 'cssPageWeaver_panel';
|
||||
|
||||
// Create base input to toggle Menu
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.id = 'cssPageWeaver_toggle-panel';
|
||||
checkbox.name = 'toggle-panel';
|
||||
checkbox.checked = this.isPanelOpen()
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.htmlFor = 'cssPageWeaver_toggle-panel';
|
||||
|
||||
const openSpan = document.createElement('span');
|
||||
openSpan.id = 'panel-open';
|
||||
openSpan.textContent = '−';
|
||||
|
||||
const closedSpan = document.createElement('span');
|
||||
closedSpan.id = 'panel-closed';
|
||||
closedSpan.textContent = '≡';
|
||||
|
||||
label.appendChild(openSpan);
|
||||
label.appendChild(closedSpan);
|
||||
|
||||
this.appendChild(checkbox);
|
||||
this.appendChild(label);
|
||||
|
||||
// Convert the features object to an array
|
||||
const featuresArray = Object.values(cssPageWeaver.features);
|
||||
|
||||
// Lets filter to only features with control panel
|
||||
const featuresWithPanels = featuresArray.filter(feature => feature.ui && feature.ui.panel !== null && feature.ui.panel !== undefined);
|
||||
|
||||
// Append the panels to the panel container
|
||||
featuresWithPanels.forEach(feature => {
|
||||
formContainer.insertAdjacentElement('beforeEnd', feature.ui.panel);
|
||||
});
|
||||
|
||||
// Create Hidden Feature Panel
|
||||
this.createHiddenFeaturePanel(featuresArray, formContainer)
|
||||
|
||||
// Append panel container to main element
|
||||
this.appendChild(formContainer);
|
||||
|
||||
}
|
||||
|
||||
createHiddenFeaturePanel(featuresArray, formContainer){
|
||||
// Create a list of active Hook or script without UI
|
||||
const featuresWithoutPanels = featuresArray.filter(feature => !feature.ui);
|
||||
|
||||
// List these elements on the interface if there are any
|
||||
if(featuresWithoutPanels.length > 0){
|
||||
const details = document.createElement('details');
|
||||
details.id = "hidden-features"
|
||||
|
||||
// Create Summary with title
|
||||
const summary = document.createElement('summary');
|
||||
const title = document.createElement('h1');
|
||||
title.textContent = "Also active";
|
||||
const span = document.createElement('span');
|
||||
span.textContent = `${featuresWithoutPanels.length} plugin${featuresWithoutPanels.length > 1 ? 's' : ''}`
|
||||
|
||||
// Append Title to summary
|
||||
summary.appendChild(title)
|
||||
summary.appendChild(span)
|
||||
|
||||
// Create list
|
||||
const ul = document.createElement('ul');
|
||||
|
||||
// Create a list of features without panels
|
||||
featuresWithoutPanels.forEach(feature => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = feature.id;
|
||||
ul.appendChild(li);
|
||||
});
|
||||
|
||||
// Append elements to details container
|
||||
details.appendChild(summary)
|
||||
details.appendChild(ul)
|
||||
|
||||
// Append unlisted features container to main container
|
||||
formContainer.insertAdjacentElement('beforeEnd', details);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*-- Features --*/
|
||||
|
||||
/**
|
||||
*/
|
||||
loopFeatures_attachPanel() {
|
||||
Object.values(cssPageWeaver.features).forEach(feature => {
|
||||
if(feature.ui){
|
||||
// Create a UI panel for the feature
|
||||
feature.ui.panel = this.createPanel(feature.id, feature.ui);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/*-- Script --*/
|
||||
|
||||
|
||||
/**
|
||||
* Registers all script features
|
||||
*
|
||||
* @param {Object} data - Dataset to loop for registration.
|
||||
* @returns {Promise<void>} - A promise that resolves when all features of the specified type are registered.
|
||||
*/
|
||||
async runAllScript(data){
|
||||
|
||||
async function runScript(scriptPromise, parameters){
|
||||
|
||||
// Await the script promise to get the script
|
||||
const script = await scriptPromise;
|
||||
|
||||
// If the script or its default export is not available, exit the function
|
||||
if(!script || !script.default){
|
||||
return
|
||||
}
|
||||
|
||||
new script.default(parameters)
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
// If data is an array, iterate over each item directly
|
||||
for (const item of data) {
|
||||
await runScript(item);
|
||||
}
|
||||
} else {
|
||||
// If data is an object,
|
||||
for (const key in data) {
|
||||
if (data.hasOwnProperty(key) && data[key].script) {
|
||||
await runScript(data[key].script, data[key].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*-- Events --*/
|
||||
|
||||
/**
|
||||
* Connects the custom element to the DOM.
|
||||
*/
|
||||
async connectedCallback () {
|
||||
this.addEventListener('click', this);
|
||||
this.addEventListener('input', this);
|
||||
|
||||
document.addEventListener('cssPageWeaver-dictInit', this.setup.bind(this));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles events for this Web Component.
|
||||
* @param {Event} event - The event object.
|
||||
*/
|
||||
handleEvent (event) {
|
||||
let featureId = this.findParentID(event)
|
||||
this[`on${event.type}`](event, featureId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find subpanel ID targeted by event
|
||||
* @param {event} - the event object
|
||||
*/
|
||||
findParentID(event){
|
||||
let parent = event.target.parentNode;
|
||||
while (parent) {
|
||||
if (parent.id && parent.id.includes('-form')) {
|
||||
const id = parent.id.split('-form')[0];
|
||||
return id
|
||||
break;
|
||||
}
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles the click event for a feature.
|
||||
*
|
||||
* @param {Event} event - The click event object.
|
||||
* @param {string} featureId - The ID of the feature being clicked.
|
||||
*/
|
||||
onclick (event, featureId) {
|
||||
this.togglePanel(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the input event for a feature.
|
||||
*
|
||||
* @param {Event} event - The input event object.
|
||||
* @param {string} featureId - The ID of the feature being interacted with.
|
||||
*/
|
||||
oninput (event, featureId) {
|
||||
// If the input event is for a toggle element
|
||||
if(cssPageWeaver.ui.event && event.target.id == `${featureId}-toggle` ){
|
||||
cssPageWeaver.ui.event[featureId].toggleState = event.target.checked
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the state of a panel based on the event target.
|
||||
* @param {Event} event - The event object that triggered the toggle action.
|
||||
*/
|
||||
togglePanel(event){
|
||||
// Handle Toogle
|
||||
if(event.target.id == "cssPageWeaver_toggle-panel"){
|
||||
let isOpen = this.isPanelOpen()
|
||||
localStorage.setItem('gui_toggle' + cssPageWeaver.docTitle, !isOpen)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the panel is open or not.
|
||||
* @returns {boolean} - Whether the panel is open or not.
|
||||
*/
|
||||
isPanelOpen(){
|
||||
return localStorage.getItem('gui_toggle' + cssPageWeaver.docTitle) == 'true' || false
|
||||
}
|
||||
|
||||
/*-- Helpers --*/
|
||||
|
||||
/**
|
||||
* This function associate a keydown event to a function
|
||||
* Important: scope is document wide, when above events are component specific
|
||||
* @param {array} keyArray - Keyboard keys combinaison to listen to
|
||||
* @param {function} callback - Function to call when right selection is pressed
|
||||
*/
|
||||
addKeydownListener(keyArray, callback) {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
const isKeyPressed = keyArray.every(key => {
|
||||
if (key === 'shiftKey') return event.shiftKey;
|
||||
if (key === 'ctrlKey') return event.ctrlKey;
|
||||
if (key === 'altKey') return event.altKey;
|
||||
if (key === 'metaKey') return event.metaKey;
|
||||
return event.key === key;
|
||||
});
|
||||
|
||||
if (isKeyPressed) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
// Store shortcut information for convenience
|
||||
cssPageWeaver.ui.shortcut_index.push(`${keyArray.join(" + ")} is set for ${callback.name}`)
|
||||
}
|
||||
|
||||
|
||||
/*-- Setup component --*/
|
||||
setup (){
|
||||
|
||||
this.extendSharedDict()
|
||||
|
||||
// Load basic CSS assets
|
||||
this.appendCSStoDOM(`${cssPageWeaver.directory.interface}/css/panel.css`);
|
||||
|
||||
// Load features CSS
|
||||
cssPageWeaver.stylesheet.features.forEach(path => this.appendCSStoDOM(path))
|
||||
|
||||
// Create and populate main Panel
|
||||
this.loopFeatures_attachPanel()
|
||||
this.createMainPanel()
|
||||
|
||||
// Register plugins scripts in a PagedJs after render event
|
||||
this.runAllScript(cssPageWeaver.features)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default CssPageWeaver_GUI
|
||||
160
public/csspageweaver/modules/pre_render.js
Normal file
160
public/csspageweaver/modules/pre_render.js
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* @classdesc This are methods shared between render.js and frame_render.js
|
||||
* @author Benjamin G. <ecrire@bnjm.eu>, Julie Blanc <contact@julie-blanc.fr>
|
||||
* @tutorial https://gitlab.com/csspageweaver/csspageweaver/-/wikis/home
|
||||
*/
|
||||
|
||||
import { Handler } from '../lib/paged.esm.js';
|
||||
|
||||
class CssPageWeaver_PreRender{
|
||||
constructor(){
|
||||
// Object of container shortcut
|
||||
this.container = {
|
||||
origin: document.body
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*-- CSS --*/
|
||||
|
||||
/**
|
||||
* Append CSS Style as link
|
||||
* @param {string} path CSS rules to append
|
||||
* @param {string} destination destination elementto append style element
|
||||
* @param {string} name name to append as a data-attribute
|
||||
*/
|
||||
appendStyleLink(path, destination, name) {
|
||||
|
||||
if(!path){
|
||||
return
|
||||
}
|
||||
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = path;
|
||||
link.media = 'screen';
|
||||
link.setAttribute(name ? name : 'data-csspageweaver-frame', true)
|
||||
|
||||
destination.appendChild(link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append CSS Style as style element
|
||||
* @param {string} style CSS rules to append
|
||||
* @param {string} destination destination elementto append style element
|
||||
* @param {string} name name to append as a data-attribute
|
||||
*/
|
||||
appendStyleElement(style, destination, name) {
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.textContent = style;
|
||||
styleElement.setAttribute(name ? name : 'data-csspageweaver-frame', true)
|
||||
destination.appendChild(styleElement);
|
||||
}
|
||||
|
||||
/*-- DOM --*/
|
||||
|
||||
clearElement(container){
|
||||
container.innerHTML = ""
|
||||
}
|
||||
|
||||
removeElements(query, container){
|
||||
const headStyles = container.querySelectorAll(query)
|
||||
headStyles.forEach(style => style.remove());
|
||||
}
|
||||
|
||||
cloneElements(query, container, destination){
|
||||
const els = container.querySelectorAll(query);
|
||||
|
||||
els.forEach(el => {
|
||||
const _el = el.cloneNode(true);
|
||||
destination.appendChild(_el);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a copy of within a container to a document fragment, optionally keeping the original elements.
|
||||
*
|
||||
* @param {Element} container - The container whose child elements need to be cloned.
|
||||
* @param {boolean} keep - Flag indicating whether to keep the original elements.
|
||||
* @returns {DocumentFragment} A document fragment containing the cloned elements.
|
||||
*/
|
||||
storeElements(container, keep) {
|
||||
// Create a document fragment
|
||||
let content = document.createDocumentFragment();
|
||||
// Clone content
|
||||
container.childNodes.forEach(child => {
|
||||
if (child.nodeType === 1 && (child.tagName !== 'SCRIPT' && !child.tagName.includes('CSSPAGEWEAVER'))) {
|
||||
const clonedChild = child.cloneNode(true);
|
||||
content.appendChild(clonedChild);
|
||||
|
||||
if (!keep) {
|
||||
child.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a unique identifier for each element within a container.
|
||||
*
|
||||
* @param {Element} container - The container whose elements need unique identifiers.
|
||||
* @returns {void}
|
||||
*/
|
||||
setUniqueIdentfier(container) {
|
||||
const elements = container.querySelectorAll('*');
|
||||
let parentCount = -1;
|
||||
// Iterate over each element and assign a unique identifier using the index
|
||||
elements.forEach((el, i) => {
|
||||
parentCount = (el.tagName === 'SECTION' || el.tagName === 'ARTICLE') ? parentCount + 1 : parentCount;
|
||||
let prefix = el.closest('article, section') ? String.fromCharCode(65 + parentCount) : '';
|
||||
el.setAttribute('data-unique-identifier', `${prefix}-${i}`);
|
||||
});
|
||||
}
|
||||
|
||||
/* Paged JS */
|
||||
|
||||
/**
|
||||
* Registers all hook features
|
||||
*
|
||||
* @param {Object} data - Dataset to loop for registration.
|
||||
* @param {Object} previewer - PagedJs instance to register
|
||||
* @returns {Promise<void>} - A promise that resolves when all features of the specified type are registered.
|
||||
*/
|
||||
async registerAllHook(hooks, previewer) {
|
||||
|
||||
/**
|
||||
* Register features pagedJs custom hook.
|
||||
* Hook are function happening at certain time of PagedJs workflow
|
||||
* @param {Object} hookPromise - The import object for the feature.
|
||||
* @param {string} id - The feature Id for debugging
|
||||
*/
|
||||
async function registerHook(hookPromise, id) {
|
||||
const hook = await hookPromise;
|
||||
|
||||
if (!hook) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handlerClass = hook.default || Object.values(hook)[0];
|
||||
|
||||
// Ensure the handlerClass is a valid Handler class
|
||||
if (handlerClass.prototype instanceof Handler) {
|
||||
try {
|
||||
previewer.registerHandlers(handlerClass);
|
||||
} catch (error) {
|
||||
console.error(`An error occurred while registering the handler ${id ? `for ${id}` : ``}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const hookPromise of hooks) {
|
||||
await registerHook(hookPromise);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CssPageWeaver_PreRender
|
||||
58
public/csspageweaver/modules/render.js
Normal file
58
public/csspageweaver/modules/render.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @classdesc This take content, paginates it and return pages to DOM. Simple.
|
||||
* @author Benjamin G. <ecrire@bnjm.eu>, Julie Blanc <contact@julie-blanc.fr>
|
||||
* @tutorial https://gitlab.com/csspageweaver/csspageweaver/-/wikis/home
|
||||
*/
|
||||
|
||||
import { Previewer } from '../lib/paged.esm.js';
|
||||
import CssPageWeaver_PreRender from './pre_render.js';
|
||||
|
||||
class CssPageWeaver_SimpleRender extends CssPageWeaver_PreRender{
|
||||
constructor(){
|
||||
super()
|
||||
|
||||
// Futur Parsed HTML to store in order to paginate
|
||||
this.content = null
|
||||
|
||||
// Hook and CSS to pass to pagedJs. Can be edit.
|
||||
this.hook = []
|
||||
this.css = []
|
||||
|
||||
this.interface = null
|
||||
console.log('CSS Page Weaver simple view initialized');
|
||||
}
|
||||
|
||||
async setup(){
|
||||
// Clone the content elements
|
||||
this.content = this.storeElements(this.container.origin, false);
|
||||
|
||||
// Unique Id
|
||||
// this.setUniqueIdentfier(this.content);
|
||||
|
||||
this.setView()
|
||||
}
|
||||
|
||||
async setView(){
|
||||
// Set up the previewer
|
||||
this.previewer = new Previewer();
|
||||
await this.registerAllHook(this.hook, this.previewer);
|
||||
|
||||
if(typeof this.interface == 'string' && this.interface.length > 0){
|
||||
this.appendStyleLink(this.interface, document.head)
|
||||
}
|
||||
|
||||
try {
|
||||
// Await the preview operation
|
||||
const pages = await this.previewer.preview(this.content, this.css, this.destination);
|
||||
|
||||
// Log the result
|
||||
console.log('Rendered', pages.total, 'pages.');
|
||||
|
||||
} catch (error) {
|
||||
// Handle any errors that occur during the preview operation
|
||||
console.error('Error during pagination:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CssPageWeaver_SimpleRender
|
||||
1
public/csspageweaver/plugins/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Hi! Just need to ensure folder remain tracked, even empty.
|
||||
1
public/csspageweaver/plugins/baseline/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/baseline/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
8
public/csspageweaver/plugins/baseline/README.md
Normal file
8
public/csspageweaver/plugins/baseline/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: baseline
|
||||
tags: recommended, stable, boilerplate, gui
|
||||
description: A simple helper to see custom baseline grid.
|
||||
---
|
||||
|
||||
|
||||
|
||||
21
public/csspageweaver/plugins/baseline/baseline.css
Normal file
21
public/csspageweaver/plugins/baseline/baseline.css
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/* BASELINE -------------------------------------------*/
|
||||
|
||||
:root {
|
||||
--pagedjs-baseline: 12px;
|
||||
--pagedjs-baseline-position: 0px;
|
||||
--pagedjs-baseline-color: cyan;
|
||||
}
|
||||
|
||||
@media screen{
|
||||
.pagedjs_pagebox {
|
||||
background: linear-gradient(transparent 0%, transparent calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) var(--pagedjs-baseline)), transparent;
|
||||
background-size: 100% var(--pagedjs-baseline);
|
||||
background-repeat: repeat-y;
|
||||
background-position-y: var(--pagedjs-baseline-position);
|
||||
|
||||
}
|
||||
|
||||
.no-baseline .pagedjs_pagebox{
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
100
public/csspageweaver/plugins/baseline/baseline.js
Normal file
100
public/csspageweaver/plugins/baseline/baseline.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* @name Baseline
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/baseline/ }
|
||||
*/
|
||||
|
||||
|
||||
export default function baseline() {
|
||||
let body = cssPageWeaver.ui.body
|
||||
let fileTitle = cssPageWeaver.docTitle;
|
||||
let parameters = cssPageWeaver.features.baseline.parameters || {}
|
||||
let isParametersSet = Object.keys(parameters).length > 0
|
||||
|
||||
/* BASELINE ----------------------------------------------------------------------------------------------------
|
||||
----------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Set baseline onload */
|
||||
let baseline = {}
|
||||
|
||||
// set default value
|
||||
baseline.default = {
|
||||
size: 32 ,
|
||||
position: 0
|
||||
}
|
||||
|
||||
baseline.toggle = {}
|
||||
baseline.toggle.value = localStorage.getItem('baselineToggle_' + fileTitle)
|
||||
baseline.toggle.input = cssPageWeaver.ui.baseline.toggleInput
|
||||
|
||||
baseline.button = cssPageWeaver.ui.baseline.toggleLabel
|
||||
|
||||
baseline.size = {}
|
||||
baseline.size.value = parameters.size || baseline.default.size
|
||||
baseline.size.input = document.querySelector('#size-baseline')
|
||||
|
||||
baseline.position = {}
|
||||
baseline.position.value = parameters.position || baseline.default.position
|
||||
baseline.position.input = document.querySelector('#position-baseline')
|
||||
|
||||
|
||||
/* */
|
||||
/* Retrieve previous sessions */
|
||||
baseline.size.value = localStorage.getItem('baselineSize_' + fileTitle) || baseline.size.value
|
||||
baseline.position.value = localStorage.getItem('baselinePosition_' + fileTitle) || baseline.position.value
|
||||
|
||||
/* */
|
||||
/* DOM edit */
|
||||
|
||||
/* Toggle */
|
||||
if(baseline.toggle.value == "no-baseline"){
|
||||
body.classList.add('no-baseline');
|
||||
baseline.toggle.input.checked = false;
|
||||
}else if(baseline.toggle.value == "baseline"){
|
||||
body.classList.remove('no-baseline');
|
||||
baseline.toggle.input.checked = true;
|
||||
}else{
|
||||
body.classList.add('no-baseline');
|
||||
localStorage.setItem('baselineToggle_' + fileTitle, 'no-baseline'); //baselineButton.querySelector(".button-hide").classList.remove("button-not-selected");
|
||||
baseline.toggle.input.checked = false;
|
||||
}
|
||||
|
||||
/* Set baseline size and position on load */
|
||||
baseline.size.input.value = baseline.size.value
|
||||
document.documentElement.style.setProperty('--pagedjs-baseline', baseline.size.value + 'px');
|
||||
baseline.position.input.value = baseline.position.value
|
||||
document.documentElement.style.setProperty('--pagedjs-baseline-position', baseline.position.value + 'px');
|
||||
|
||||
|
||||
/* */
|
||||
/* Event listenner */
|
||||
|
||||
/* Toggle event */
|
||||
baseline.toggle.input.addEventListener("input", (e) => {
|
||||
if(e.target.checked){
|
||||
/* see baseline */
|
||||
body.classList.remove('no-baseline');
|
||||
localStorage.setItem('baselineToggle_' + fileTitle, 'baseline');
|
||||
}else{
|
||||
/* hide baseline */
|
||||
body.classList.add('no-baseline');
|
||||
localStorage.setItem('baselineToggle_' + fileTitle, 'no-baseline');
|
||||
}
|
||||
});
|
||||
|
||||
/* Change baseline size on input */
|
||||
document.querySelector("#size-baseline").addEventListener("input", (e) => {
|
||||
baseline.size.value = e.target.value
|
||||
document.documentElement.style.setProperty('--pagedjs-baseline', baseline.size.value + 'px');
|
||||
localStorage.setItem('baselineSize_' + fileTitle, baseline.size.value);
|
||||
});
|
||||
|
||||
|
||||
/* Change baseline position on input */
|
||||
document.querySelector("#position-baseline").addEventListener("input", (e) => {
|
||||
baseline.position.value = e.target.value
|
||||
document.documentElement.style.setProperty('--pagedjs-baseline-position', baseline.position.value + 'px');
|
||||
localStorage.setItem('baselinePosition_' + fileTitle, baseline.position.value);
|
||||
});
|
||||
|
||||
}
|
||||
13
public/csspageweaver/plugins/baseline/config.json
Normal file
13
public/csspageweaver/plugins/baseline/config.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "Baseline",
|
||||
"description": "",
|
||||
"version": "1.0",
|
||||
"ui": {
|
||||
"title": "Baseline Grid",
|
||||
"description": "This Toogle a baseline on pages",
|
||||
"template": "template.html",
|
||||
"toggle": true
|
||||
},
|
||||
"script": "baseline.js",
|
||||
"stylesheet": "baseline.css"
|
||||
}
|
||||
8
public/csspageweaver/plugins/baseline/template.html
Normal file
8
public/csspageweaver/plugins/baseline/template.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<div class="panel-group-values">
|
||||
<label for="size-baseline">Size (px)</label>
|
||||
<input type="number" id="size-baseline" name="size-baseline" min="1" max="100" value="12">
|
||||
</div>
|
||||
<div class="panel-group-values">
|
||||
<label for="position-baseline" id="label-position">Position (px)</label>
|
||||
<input type="number" id="position-baseline" name="position-baseline" value="0">
|
||||
</div>
|
||||
1
public/csspageweaver/plugins/floatPage/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/floatPage/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
0
public/csspageweaver/plugins/floatPage/.hugo_build.lock
Normal file
0
public/csspageweaver/plugins/floatPage/.hugo_build.lock
Normal file
74
public/csspageweaver/plugins/floatPage/README.md
Normal file
74
public/csspageweaver/plugins/floatPage/README.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
name: floatPage
|
||||
tags: early-stage, feedback-wanted
|
||||
description: Handles floats that are positioned at the top or bottom of pages.
|
||||
---
|
||||
|
||||
|
||||
# Float page elements (paged.js)
|
||||
|
||||
This plugin handles floats that are positioned at the top or bottom of pages.
|
||||
This feature has traditionally been used in print publications in which figures and photos are moved to the top or bottom of columns or pages, along with their captions.
|
||||
|
||||
You need to use [csstree.js](https://github.com/csstree/csstree) in order to transform custom properties.
|
||||
If you use CSS Page Weaver is inclued by default
|
||||
|
||||
|
||||
## How to install
|
||||
|
||||
**With CSS Page Weaver**
|
||||
|
||||
Register the `floatPage` plugin in your `manifest.json`:
|
||||
|
||||
```json
|
||||
[
|
||||
"floatPage",
|
||||
// other plugins
|
||||
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
|
||||
**Without CSS Page Weaver**
|
||||
|
||||
Include both csstree and the fullPage script in your HTML `<head>`:
|
||||
|
||||
```html
|
||||
<script src="js/csstree.min.js"></script>
|
||||
<script type="module" src="path/to/fullPage/floatPage.js"></script>
|
||||
```
|
||||
|
||||
Don’t forget to update the path to the paged.esm.js module in the import statement before using the script.
|
||||
|
||||
```js
|
||||
import { Handler } from '/path/to/paged.esm.js'
|
||||
```
|
||||
|
||||
## How to use it
|
||||
|
||||
In the CSS, add the following custom property to the elements (using IDs or classes) that you want to float to the top or bottom of pages:
|
||||
|
||||
```css
|
||||
elem{
|
||||
--pagedjs-float-page: top;
|
||||
}
|
||||
```
|
||||
|
||||
- `--pagedjs-float-page: top` → The element will be placed at the top of the current page.
|
||||
- `--pagedjs-float-page: bottom` → The element will be placed at the bottom of the current page.
|
||||
- `--pagedjs-float-page: next-page` → The element will be placed at the top of the next page.
|
||||
|
||||
Notes:
|
||||
- this script works on any elements, even if the element contains several child elements;
|
||||
- the element will be reinserted into its original parent if that parent still exists on the page.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
- [pagedjs.org](https://www.pagedjs.org/)
|
||||
- [csstree.js](https://github.com/csstree/csstree)
|
||||
|
||||
MIT licence, Julie Blanc, 2025
|
||||
|
||||
|
||||
8
public/csspageweaver/plugins/floatPage/config.json
Normal file
8
public/csspageweaver/plugins/floatPage/config.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Float PAge",
|
||||
"description": "",
|
||||
"author": ["Julie Blanc"],
|
||||
"license": "MIT License",
|
||||
"version": "1.0",
|
||||
"hook": "floatPage.js"
|
||||
}
|
||||
153
public/csspageweaver/plugins/floatPage/floatPage.js
Normal file
153
public/csspageweaver/plugins/floatPage/floatPage.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
* @name Float Page v1.0
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/floatPage }
|
||||
*/
|
||||
|
||||
import { Handler } from '/csspageweaver/lib/paged.esm.js';
|
||||
|
||||
export class floatPage extends Handler {
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
this.selectorBottom = new Set();
|
||||
this.selectorTop = new Set();
|
||||
this.selectorNextPage = new Set();
|
||||
this.nextPageElem = new Set();
|
||||
this.nextPageElemTemp = new Set();
|
||||
}
|
||||
|
||||
|
||||
onDeclaration(declaration, dItem, dList, rule) {
|
||||
// Read customs properties
|
||||
if (declaration.property == "--pagedjs-float-page") {
|
||||
// get selector of the declaration (NOTE: need csstree.js)
|
||||
let selector = csstree.generate(rule.ruleNode.prelude);
|
||||
// Push selector in correct set
|
||||
if(declaration.value.value.includes("bottom")) {
|
||||
this.selectorBottom.add(selector);
|
||||
}else if(declaration.value.value.includes("top")) {
|
||||
this.selectorTop.add(selector);
|
||||
}else if(declaration.value.value.includes("next-page")) {
|
||||
this.selectorNextPage.add(selector);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
afterParsed(parsed){
|
||||
// bottom
|
||||
for (let item of this.selectorBottom) {
|
||||
let elems = parsed.querySelectorAll(item);
|
||||
for (let elem of elems) {
|
||||
elem.classList.add("float-page_bottom");
|
||||
}
|
||||
}
|
||||
|
||||
// top
|
||||
for (let item of this.selectorTop) {
|
||||
let elems = parsed.querySelectorAll(item);
|
||||
for (let elem of elems) {
|
||||
elem.classList.add("float-page_top");
|
||||
}
|
||||
}
|
||||
|
||||
// next-page
|
||||
for (let item of this.selectorNextPage) {
|
||||
let elems = parsed.querySelectorAll(item);
|
||||
for (let elem of elems) {
|
||||
elem.classList.add("float-page_next-page");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
afterPageLayout(pageElement, page, breakToken){
|
||||
|
||||
// TOP (positionning)
|
||||
let floatTopPage = pageElement.querySelector(".float-page_top");
|
||||
if(floatTopPage){
|
||||
let selector = buildCssSelector(floatTopPage.parentNode);
|
||||
parent = pageElement.querySelector(selector)
|
||||
parent.insertBefore(floatTopPage, parent.firstChild);
|
||||
}
|
||||
|
||||
// BOTTOM (positionning)
|
||||
let floatBottomPage = pageElement.querySelector(".float-page_bottom");
|
||||
if(floatBottomPage){
|
||||
let selector = buildCssSelector(floatBottomPage.parentNode);
|
||||
parent = pageElement.querySelector(selector);
|
||||
parent.insertBefore(floatBottomPage, parent.firstChild);
|
||||
floatBottomPage.style.position = "absolute";
|
||||
floatBottomPage.style.bottom = "0px";
|
||||
}
|
||||
|
||||
// NEXT PAGE (positionning into parent section)
|
||||
for (let elem of this.nextPageElemTemp) {
|
||||
let cleanedSelector = elem.parentSelector.replace(/^#document-fragment\s*>\s*/, '');
|
||||
let parent = pageElement.querySelector(cleanedSelector);
|
||||
|
||||
let node = elem.elem;
|
||||
if(parent){
|
||||
parent.insertBefore(node, parent.firstChild);
|
||||
}
|
||||
}
|
||||
this.nextPageElemTemp.clear();
|
||||
|
||||
|
||||
}
|
||||
|
||||
// NEXT PAGE (pass to next page) -------------------
|
||||
|
||||
renderNode(clone, node) {
|
||||
if (node.nodeType == 1 && node.classList.contains("float-page_next-page")) {
|
||||
clone.remove();
|
||||
this.nextPageElem.add({ elem: node, parentSelector: buildCssSelector(node.parentNode) });
|
||||
}
|
||||
}
|
||||
|
||||
onPageLayout(page, Token, layout) {
|
||||
for (let elem of this.nextPageElem) {
|
||||
page.insertBefore(elem.elem, page.firstChild);
|
||||
this.nextPageElemTemp.add(elem);
|
||||
}
|
||||
this.nextPageElem.clear();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// FONCTIONS --------------------------------------------------------------------------
|
||||
|
||||
// Function to build the CSS selector from the element and its parents
|
||||
function buildCssSelector(element) {
|
||||
let selector = [];
|
||||
let current = element;
|
||||
|
||||
while (current) {
|
||||
if (current.classList && current.classList.contains('pagedjs_page_content')) {
|
||||
break; // Stop if the parent with class 'pagedjs_page_content' is found
|
||||
}
|
||||
|
||||
let currentSelector = current.nodeName.toLowerCase();
|
||||
|
||||
if (current.id) {
|
||||
currentSelector += `[data-id="${current.id}"]`;
|
||||
}
|
||||
|
||||
if (current.className) {
|
||||
currentSelector += `.${current.className.split(' ').join('.')}`;
|
||||
}
|
||||
|
||||
selector.unshift(currentSelector);
|
||||
current = current.parentNode;
|
||||
}
|
||||
|
||||
selector = selector.join(' > ');
|
||||
// selector = selector.replace(/#document-fragment( > )?/g, ''); // Clean the CSS selector by removing '#document-fragment'
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>Categories on </title>
|
||||
<link>//localhost:1313/categories/</link>
|
||||
<description>Recent content in Categories on </description>
|
||||
<generator>Hugo</generator>
|
||||
<language>en</language>
|
||||
<atom:link href="//localhost:1313/categories/index.xml" rel="self" type="application/rss+xml" />
|
||||
</channel>
|
||||
</rss>
|
||||
11
public/csspageweaver/plugins/floatPage/public/index.xml
Normal file
11
public/csspageweaver/plugins/floatPage/public/index.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title></title>
|
||||
<link>//localhost:1313/</link>
|
||||
<description>Recent content on </description>
|
||||
<generator>Hugo</generator>
|
||||
<language>en</language>
|
||||
<atom:link href="//localhost:1313/index.xml" rel="self" type="application/rss+xml" />
|
||||
</channel>
|
||||
</rss>
|
||||
11
public/csspageweaver/plugins/floatPage/public/sitemap.xml
Normal file
11
public/csspageweaver/plugins/floatPage/public/sitemap.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
<url>
|
||||
<loc>//localhost:1313/</loc>
|
||||
</url><url>
|
||||
<loc>//localhost:1313/categories/</loc>
|
||||
</url><url>
|
||||
<loc>//localhost:1313/tags/</loc>
|
||||
</url>
|
||||
</urlset>
|
||||
11
public/csspageweaver/plugins/floatPage/public/tags/index.xml
Normal file
11
public/csspageweaver/plugins/floatPage/public/tags/index.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>Tags on </title>
|
||||
<link>//localhost:1313/tags/</link>
|
||||
<description>Recent content in Tags on </description>
|
||||
<generator>Hugo</generator>
|
||||
<language>en</language>
|
||||
<atom:link href="//localhost:1313/tags/index.xml" rel="self" type="application/rss+xml" />
|
||||
</channel>
|
||||
</rss>
|
||||
1
public/csspageweaver/plugins/footnotesFix/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/footnotesFix/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
95
public/csspageweaver/plugins/footnotesFix/README.md
Normal file
95
public/csspageweaver/plugins/footnotesFix/README.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# Plugin to fix footnotes reset issue
|
||||
|
||||
This plugin fix the issue of footnote reset.
|
||||
|
||||
You can use the current method to declare footnotes:
|
||||
|
||||
```CSS
|
||||
@page {
|
||||
@footnote {
|
||||
float: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.pagedjs_footnote {
|
||||
float: footnote;
|
||||
}
|
||||
```
|
||||
|
||||
This style is also added to the default stylesheet `footnotes.css` of this plugin. You can delete it if you have already declared footnotes in your own stylesheet (don't forget to remove it from the `config.json` as well).
|
||||
|
||||
|
||||
## How to use the plugin
|
||||
|
||||
Add this folder to `csspageweaver/plugins/`.
|
||||
|
||||
Call the plugin in `csspageweaver/manifest.json`:
|
||||
|
||||
```json
|
||||
"plugins": [
|
||||
"footnotesFix",
|
||||
// other plugins ...
|
||||
],
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
In `manifest.json`, you can modify/add some parameters:
|
||||
|
||||
```json
|
||||
"plugins":{
|
||||
"footnotesFix"
|
||||
},
|
||||
"pluginsParameters":{
|
||||
"footnotesFix": {
|
||||
"selector": ".footnote",
|
||||
"reset": ".chapter"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
All the parameters are optional.
|
||||
|
||||
- `selector` → CSS selector for the note element (must be inline in the HTML), by default is `.footnote`
|
||||
- `reset` → CSS selector where you want reset note counter. If you want to reset on the page: `page`
|
||||
|
||||
|
||||
## Notes in HTML
|
||||
|
||||
In your HTML, the note must be a `<span>` inserted in the text, like this:
|
||||
|
||||
```HTML
|
||||
Donec tincidunt, odio vel vestibulum sollicitudin, nibh dolor tempor sapien, ac laoreet
|
||||
sem felis ut purus. <span class=".footnote">Vestibulum neque ex, ullamcorper sit
|
||||
amet diam sed, pharetra laoreet sem.</span> Morbi cursus bibendum consectetur. Nullam vel
|
||||
lacus congue nibh pulvinar maximus sit amet eu risus. Curabitur semper odio mauris, nec
|
||||
imperdiet velit pharetra non. Aenean accumsan nulla ac ex iaculis interdum.
|
||||
```
|
||||
|
||||
You can use the [inline_notes` plugin](https://gitlab.com/csspageweaver/plugins/inline_notes) to create these span elements from listed notes, which are more common in conversion tools like Pandoc.
|
||||
|
||||
The inline_notes plugin should be called before the footnotes plugin in the `manifest.json`:
|
||||
|
||||
|
||||
```json
|
||||
"plugins": [
|
||||
"inline_notes",
|
||||
"footnotes_fix",
|
||||
// other plugins ...
|
||||
],
|
||||
```
|
||||
|
||||
## Styling call & footer
|
||||
|
||||
It's possible to change the styles of call notes and marker notes directly in your stylesheet like in the following code:
|
||||
|
||||
```CSS
|
||||
::footnote-call{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
::footnote-marker{
|
||||
font-weight: bold;
|
||||
}
|
||||
```
|
||||
|
||||
9
public/csspageweaver/plugins/footnotesFix/config.json
Normal file
9
public/csspageweaver/plugins/footnotesFix/config.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "Footnotes",
|
||||
"description": "Fix footnote reset",
|
||||
"author": ["Julie Blanc"],
|
||||
"licence": "MIT",
|
||||
"version": "1.0",
|
||||
"hook": "footnotes.js",
|
||||
"stylesheet": "footnotes.css"
|
||||
}
|
||||
9
public/csspageweaver/plugins/footnotesFix/footnotes.css
Normal file
9
public/csspageweaver/plugins/footnotesFix/footnotes.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
@page {
|
||||
@footnote {
|
||||
float: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.footnote {
|
||||
float: footnote;
|
||||
}
|
||||
85
public/csspageweaver/plugins/footnotesFix/footnotes.js
Normal file
85
public/csspageweaver/plugins/footnotesFix/footnotes.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* @name Footnotes
|
||||
* @file Reset the way footnote are counted
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/footnotesFix/ }
|
||||
*/
|
||||
|
||||
import { Handler } from '/csspageweaver/lib/paged.esm.js';
|
||||
|
||||
export default class footnotes extends Handler {
|
||||
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
this.parameters = cssPageWeaver.features.footnotesFix.parameters;
|
||||
this.reset = this.parameters?.reset ;
|
||||
this.counter = 0;
|
||||
this.selector = this.parameters?.selector || ".footnote";
|
||||
}
|
||||
|
||||
beforeParsed(content) {
|
||||
|
||||
|
||||
let notes = content.querySelectorAll(this.selector);
|
||||
notes.forEach(function (note, index) {
|
||||
note.classList.add("pagedjs_footnote");
|
||||
});
|
||||
|
||||
|
||||
|
||||
if(this.reset){
|
||||
let elems = content.querySelectorAll(this.reset);
|
||||
elems.forEach(function (elem, index) {
|
||||
var span = document.createElement('span');
|
||||
span.classList.add("reset-fix-footnote");
|
||||
span.style.position = "absolute";
|
||||
elem.insertBefore(span, elem.firstChild);
|
||||
});
|
||||
}else{
|
||||
console.log("[footnotesFix] no reset")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
afterPageLayout(pageElement, page, breakToken){
|
||||
|
||||
if(this.reset){
|
||||
|
||||
// reset on pages
|
||||
if(this.reset === "page"){
|
||||
this.counter = 0;
|
||||
}
|
||||
|
||||
// reset on specific element
|
||||
let newchapter = pageElement.querySelector('.reset-fix-footnote');
|
||||
if(newchapter){
|
||||
this.counter = 0;
|
||||
}
|
||||
|
||||
let footnotes = pageElement.querySelectorAll(".pagedjs_footnote_content [data-note]");
|
||||
|
||||
let callnotes = pageElement.querySelectorAll('a.pagedjs_footnote');
|
||||
callnotes.forEach((call, index) => {
|
||||
|
||||
this.counter = this.counter + 1; // increment
|
||||
let num = this.counter - 1;
|
||||
|
||||
// update data-counter for call
|
||||
call.setAttribute('data-counter-footnote-increment', num);
|
||||
call.style.counterReset = "footnote " + num;
|
||||
|
||||
// update data-counter for marker
|
||||
let footnote = footnotes[index];
|
||||
let dataCounter = num + 1;
|
||||
footnote.setAttribute('data-counter-note', dataCounter);
|
||||
footnote.style.counterReset = "footnote-marker " + num;
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
1
public/csspageweaver/plugins/frenchTypoRegex/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/frenchTypoRegex/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
10
public/csspageweaver/plugins/frenchTypoRegex/config.json
Normal file
10
public/csspageweaver/plugins/frenchTypoRegex/config.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "French Typography Regex",
|
||||
"description": "A collection of regex rules for French text normalization. This file provides a set of regular expressions to clean and standardize French typography. It automatically fixes spacing, quotation marks, apostrophes, ellipses, and common orthotypographic conventions (non-breaking spaces before punctuation, French guillemets, etc.).",
|
||||
"author": ["Julie Blanc"],
|
||||
"license": "MIT License",
|
||||
"version": "1.0",
|
||||
"created": "2025-08-26",
|
||||
"updated": "2025-08-26",
|
||||
"hook": "frenchTypoRegex.js"
|
||||
}
|
||||
169
public/csspageweaver/plugins/frenchTypoRegex/frenchTypoRegex.js
Normal file
169
public/csspageweaver/plugins/frenchTypoRegex/frenchTypoRegex.js
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* @name French Typography Regex
|
||||
* @file A collection of regex rules for French text normalization
|
||||
* @description
|
||||
* This file provides a set of regular expressions to clean and standardize
|
||||
* French typography. It automatically fixes spacing, quotation marks,
|
||||
* apostrophes, ellipses, and common orthotypographic conventions
|
||||
* (non-breaking spaces before punctuation, French guillemets, etc.).
|
||||
*
|
||||
* @author Julie Blanc (contact@julie-blanc.fr)
|
||||
* @created 2025-08-26
|
||||
* @updated 2025-08-26
|
||||
* @see {@link https://gitlab.com/csspageweaver/frenchTypoRegex/ }
|
||||
*/
|
||||
|
||||
import { Handler } from '/csspageweaver/lib/paged.esm.js';
|
||||
|
||||
export default class frenchTypoRegex extends Handler {
|
||||
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
}
|
||||
|
||||
beforeParsed(content) {
|
||||
|
||||
// SPECIFIC, delete span with apostroph
|
||||
content.querySelectorAll('span[dir="rtl"]').forEach(span => {
|
||||
if (span.textContent.trim() === '’' || span.textContent.trim() === '"’"' ) {
|
||||
span.replaceWith(document.createTextNode('’'));
|
||||
}
|
||||
});
|
||||
|
||||
applyRegex(content);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
const arrayRegexFrenchTypo = [
|
||||
{
|
||||
// Remplacement des doubles espaces (ou plus) par un seul espace normal
|
||||
reg: /\s{2,}/g,
|
||||
repl: " ",
|
||||
},
|
||||
{
|
||||
// XIème = XIe
|
||||
reg: /(X|I|V)ème/g,
|
||||
repl: "$1e",
|
||||
},
|
||||
{
|
||||
// guillemets français ouvrants
|
||||
reg: /"([^\s])/g,
|
||||
repl: "«"
|
||||
},
|
||||
{
|
||||
// guillemets français fermants
|
||||
reg: /([^\s])"/g,
|
||||
repl: "»"
|
||||
},
|
||||
{
|
||||
// espace insécable avant ; : ? ! »
|
||||
reg: /\s+([;:?!»])/g,
|
||||
repl: "\u00A0$1",
|
||||
},
|
||||
{
|
||||
// espace insécable après «
|
||||
reg: /(«)\s+/g,
|
||||
repl: "$1\u00A0",
|
||||
},
|
||||
{
|
||||
// real apostrophe
|
||||
reg: /(\w)'(\w)/g,
|
||||
repl: '$1’$2'
|
||||
},
|
||||
{
|
||||
// real suspension points
|
||||
reg: /\.{3,}/g,
|
||||
repl: '\u2026'
|
||||
},
|
||||
{
|
||||
// espace insécable après certains mots de deux lettres
|
||||
reg: /\b(le|la|ce|on|il|de|du|un|au|en)\s+/gi,
|
||||
repl: '$1\u00A0'
|
||||
},
|
||||
{
|
||||
// no break space after one letter words
|
||||
reg: /\s+([a-zà])\s+/gi,
|
||||
repl: ' $1\u00A0'
|
||||
},
|
||||
{
|
||||
// no break space after first word (2-3 letter) of the sentence
|
||||
reg: /\.\s([A-ZÀ-Ö])([A-Za-zÀ-ÖØ-öø-ÿœŒ]{1,3})\s+/g,
|
||||
repl: '. $1$2\u00A0'
|
||||
},
|
||||
{
|
||||
// delete all spaces before punctuation !?;:»›”%€)].,
|
||||
reg: /\s+([!?;:»›”%€$)\]\.\,])/g,
|
||||
repl: '$1'
|
||||
},
|
||||
{
|
||||
// add narrow no break space before !?;:»›%€ (sauf http://)
|
||||
reg: /(?<!https?)[:!?;»›%€$]/g,
|
||||
repl: '\u202F$&'
|
||||
},
|
||||
{
|
||||
// delete all spaces after «‹“[(
|
||||
reg: /([«‹“\[(])\s+/g,
|
||||
repl: '$1'
|
||||
},
|
||||
{
|
||||
// add narrow no break space after «‹
|
||||
reg: /([«‹])/g,
|
||||
repl: '$1\u202F'
|
||||
},
|
||||
{
|
||||
// no break space before 'siècle'
|
||||
reg: /(X|I|V)(er|e)\s+siècle/g,
|
||||
repl: '$1$2\u00A0siècle'
|
||||
},
|
||||
{
|
||||
// Espaces insécables pour les nombres
|
||||
reg: /(\d)(?=(\d{3})+(?!\d))/g,
|
||||
repl: '$1\u202F'
|
||||
},
|
||||
{
|
||||
// no break space after figures table page chapitre ect. + number
|
||||
reg: /(figures?|tables?|planches?|chapitres?|pages?|parties?|sections?|volumes?|vol\.)\s+(\d|I|X|V)/g,
|
||||
repl: '$1\u00A0$2'
|
||||
},
|
||||
{
|
||||
// Espace insécable après "p." ou "pp." en bibliographie
|
||||
reg: /\b(pp?)\.\s*(\d+)/gi,
|
||||
repl: '$1.\u00A0$2'
|
||||
},
|
||||
{
|
||||
// Coupures possibles dans les URLs (ajout d’un zero-width space après / - _ . sauf les // initiaux)
|
||||
reg: /(https?:\/\/[^\s]+)/g,
|
||||
repl: (match) => match.replace(/(?<!:)\/(?!\/)|[-_.]/g, "$&\u200B")
|
||||
},
|
||||
];
|
||||
|
||||
function applyRegex(content) {
|
||||
|
||||
// TREE WALKER
|
||||
const walker = document.createTreeWalker(
|
||||
content,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
let node;
|
||||
while ((node = walker.nextNode())) {
|
||||
// Exclure <code> ou <pre>
|
||||
const codeParent = node.parentElement?.closest("code, pre");
|
||||
if (codeParent) continue;
|
||||
|
||||
// APPLY REGEX FROM ARRAY
|
||||
for (let i = 0; i < arrayRegexFrenchTypo.length; i++) {
|
||||
node.textContent = node.textContent.replace(arrayRegexFrenchTypo[i].reg, arrayRegexFrenchTypo[i].repl);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
1
public/csspageweaver/plugins/fullPage/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/fullPage/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
126
public/csspageweaver/plugins/fullPage/README.md
Executable file
126
public/csspageweaver/plugins/fullPage/README.md
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
---
|
||||
name: fullPage
|
||||
tags: recommended, stable
|
||||
description: Create full page elements and full spread elements in the flow of your book.
|
||||
|
||||
---
|
||||
|
||||
|
||||
# Full page elements (paged.js)
|
||||
|
||||
This script help you to create full page elements and full spread elements in the flow of your book.
|
||||
|
||||
You need to use [csstree.js](https://github.com/csstree/csstree) in order to transform custom properties.
|
||||
If you use CSS Page Maker is inclued by default
|
||||
|
||||
|
||||
## How to install
|
||||
|
||||
**With CSS Page Marker**
|
||||
|
||||
Make sure the csstree library is included in the `<head>` of your HTML:
|
||||
|
||||
|
||||
```html
|
||||
<script src="/csspageweaver/lib/csstree.min.js"></script>
|
||||
```
|
||||
|
||||
|
||||
Register the `fullPage` plugin in your `manifest.json`:
|
||||
|
||||
```json
|
||||
[
|
||||
"fullPage",
|
||||
// other plugins
|
||||
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
|
||||
**Without CSS Page Maker**
|
||||
|
||||
Include both csstree and the fullPage script in your HTML `<head>`:
|
||||
|
||||
```html
|
||||
<script src="js/csstree.min.js"></script>
|
||||
<script src="path/to/fullPage/fullPage.js"></script>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## How to use it
|
||||
|
||||
In the CSS, on the element(s) you want in full page add the following custom property(works with id and classes):
|
||||
|
||||
```css
|
||||
elem{
|
||||
--pagedjs-full-page: page
|
||||
}
|
||||
```
|
||||
|
||||
You have multiple keywords for the custom property:
|
||||
- `--pagedjs-full-page: page` → The element will be remove from flow and put in the next page.
|
||||
- `--pagedjs-full-page: left` → The element will be remove from flow and put in the next left page.
|
||||
- `--pagedjs-full-page: right` → The element will be remove from flow and put in the next right page.
|
||||
- `--pagedjs-full-page: spread` → The element will be remove from flow and put in the next spread.
|
||||
- `--pagedjs-full-page: <number>` → The element will be remove from flow and put in the page you specify (with `--pagedjs-full-page: 4`, the element is put on page number 4).
|
||||
|
||||
Note that this script works on any elements, even if the element contains several child elements.
|
||||
|
||||
|
||||
### Images in full page
|
||||
|
||||
If you want an image in full page, we advise you to use the usual `objet-fit` properties.
|
||||
|
||||
```css
|
||||
#figure{
|
||||
--pagedjs-full-page: page;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
img {
|
||||
object-fit: cover;
|
||||
object-position: 0px 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
- To change the size of you image, use `width` and `height`.
|
||||
- To change the position of your image, use `object-position`.
|
||||
- In the case of the `spread` option, all the spread will be considered, i.e. `width: 100%` cover all the spread.
|
||||
|
||||
|
||||
### Spread and central fold
|
||||
|
||||
Sometimes, when a book is binding, the elements that cover the spread need to be offset from the central fold. A custom value can be added to the page to take it into account.
|
||||
|
||||
```css
|
||||
@page {
|
||||
--pagedjs-fold: 10mm;
|
||||
}
|
||||
```
|
||||
|
||||
### Bleeds of full page and full spread elements
|
||||
|
||||
In order to avoid that your elements moves when you change the bleeds and the crop marks of your document, the bleeds of full page elements was set up to `6mm`. This is due to the way Paged.js modifies the DOM (full page elements are contained in the page sheet and depend on the dimensions of this page sheet).
|
||||
If you want to change the dimensions of these specific bleeds, you just have to change the value of the `bleedFull` variable in the first line of the `full-page.js` file
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
You can find examples of use in `css/full-page.css`.
|
||||
|
||||

|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
- [pagedjs.org](https://www.pagedjs.org/)
|
||||
- [csstree.js](https://github.com/csstree/csstree)
|
||||
|
||||
MIT licence, Julie Blanc, 2021
|
||||
|
||||
|
||||
9
public/csspageweaver/plugins/fullPage/config.json
Normal file
9
public/csspageweaver/plugins/fullPage/config.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "Full page",
|
||||
"description": "Create full page elements and full spread elements in the flow of your book",
|
||||
"licence": "MIT",
|
||||
"author": ["Julie Blanc"],
|
||||
"version": "2.0",
|
||||
"hook": "fullPage.js",
|
||||
"stylesheet": "fullPage.css"
|
||||
}
|
||||
49
public/csspageweaver/plugins/fullPage/fullPage.css
Normal file
49
public/csspageweaver/plugins/fullPage/fullPage.css
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
.pagedjs_page_fullLeft .pagedjs_full-spread_container{
|
||||
margin: 0;
|
||||
width: calc(var(--pagedjs-pagebox-width) + var(--bleed-images));
|
||||
height: calc(var(--pagedjs-pagebox-height) + var(--bleed-images)*2)!important;
|
||||
position: absolute;
|
||||
top: calc((var(--pagedjs-margin-top) + var(--bleed-images))*-1);
|
||||
left: calc((var(--pagedjs-margin-left) + var(--bleed-images))*-1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.pagedjs_page_fullRight .pagedjs_full-spread_container{
|
||||
margin: 0;
|
||||
width: calc(var(--pagedjs-pagebox-width) + var(--bleed-images));
|
||||
height: calc(var(--pagedjs-pagebox-height) + var(--bleed-images)*2)!important;
|
||||
position: absolute;
|
||||
top: calc((var(--pagedjs-margin-top) + var(--bleed-images))*-1);
|
||||
left: calc(var(--pagedjs-margin-left)*-1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.pagedjs_full-spread_content{
|
||||
margin: 0;
|
||||
width: calc(var(--pagedjs-pagebox-width)*2 + var(--bleed-images)*2);
|
||||
height: calc(var(--pagedjs-pagebox-height) + var(--bleed-images)*2)!important;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.pagedjs_page_fullLeft .pagedjs_full-spread_content{
|
||||
left: calc(var(--pagedjs-fold)*-1);
|
||||
}
|
||||
.pagedjs_page_fullRight .pagedjs_full-spread_content{
|
||||
left: calc((var(--pagedjs-pagebox-width) + var(--bleed-images))*-1 + var(--pagedjs-fold))
|
||||
}
|
||||
|
||||
.pagedjs_full-page_content {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: calc((var(--pagedjs-margin-top) + var(--bleed-images))*-1);
|
||||
}
|
||||
.pagedjs_left_page .pagedjs_full-page_content {
|
||||
width: calc(var(--pagedjs-pagebox-width) + var(--bleed-images));
|
||||
height: calc(var(--pagedjs-pagebox-height) + var(--bleed-images) + var(--bleed-images))!important;
|
||||
left: calc((var(--pagedjs-margin-left) + var(--bleed-images))*-1);
|
||||
|
||||
}
|
||||
.pagedjs_right_page .pagedjs_full-page_content {
|
||||
width: calc(var(--pagedjs-pagebox-width) + var(--bleed-images));
|
||||
height: calc(var(--pagedjs-pagebox-height) + var(--bleed-images) * 2)!important;
|
||||
left: calc(var(--pagedjs-margin-left)*-1);
|
||||
}
|
||||
294
public/csspageweaver/plugins/fullPage/fullPage.js
Normal file
294
public/csspageweaver/plugins/fullPage/fullPage.js
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
/**
|
||||
* @name Full page
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/fullPage }
|
||||
*/
|
||||
import { Handler } from '/csspageweaver/lib/paged.esm.js';
|
||||
|
||||
|
||||
let bleedFull = '6mm';
|
||||
|
||||
export default class fullPage extends Handler {
|
||||
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
this.selectorFullSpread = new Set();
|
||||
this.fullSpreadEls = new Set();
|
||||
this.selectorFullPage = new Set();
|
||||
this.fullPageEls = new Set();
|
||||
this.selectorFullRight = new Set();
|
||||
this.fullRightEls = new Set();
|
||||
this.selectorFullLeft= new Set();
|
||||
this.fullLeftEls = new Set();
|
||||
this.usedPagedEls = new Set();
|
||||
this.specificPage = new Set();
|
||||
this.specificPageClone = new Set();
|
||||
}
|
||||
|
||||
|
||||
|
||||
onDeclaration(declaration, dItem, dList, rule) {
|
||||
// Read customs properties
|
||||
if (declaration.property == "--pagedjs-full-page") {
|
||||
// get selector of the declaration (NOTE: need csstree.js)
|
||||
let selector = csstree.generate(rule.ruleNode.prelude);
|
||||
// Push selector in correct set
|
||||
if (declaration.value.value.includes("page")) {
|
||||
this.selectorFullPage.add(selector);
|
||||
}else if(declaration.value.value.includes("spread")) {
|
||||
this.selectorFullSpread.add(selector);
|
||||
}else if(declaration.value.value.includes("right")) {
|
||||
this.selectorFullRight.add(selector);
|
||||
}else if(declaration.value.value.includes("left")) {
|
||||
this.selectorFullLeft.add(selector);
|
||||
}else{
|
||||
let obj = { page: declaration.value.value, elem: selector };
|
||||
this.specificPage.add(JSON.stringify(obj));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterParsed(parsed){
|
||||
|
||||
console.log("FULL PAGE loaded");
|
||||
|
||||
// ADD pagedjs classes to elements
|
||||
for (let item of this.selectorFullPage) {
|
||||
let elems = parsed.querySelectorAll(item);
|
||||
for (let elem of elems) {
|
||||
elem.classList.add("pagedjs_full-page-elem");
|
||||
}
|
||||
}
|
||||
for (let item of this.selectorFullSpread) {
|
||||
let elems = parsed.querySelectorAll(item);
|
||||
for (let elem of elems) {
|
||||
elem.classList.add("pagedjs_full-spread-elem");
|
||||
}
|
||||
}
|
||||
for (let item of this.selectorFullLeft) {
|
||||
let elems = parsed.querySelectorAll(item);
|
||||
for (let elem of elems) {
|
||||
elem.classList.add("pagedjs_full-page-left-elem");
|
||||
}
|
||||
}
|
||||
for (let item of this.selectorFullRight) {
|
||||
let elems = parsed.querySelectorAll(item);
|
||||
for (let elem of elems) {
|
||||
elem.classList.add("pagedjs_full-page-right-elem");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SPECIFIC PAGE ------------------------------------
|
||||
this.specificPage.forEach(entry => {
|
||||
const obj = JSON.parse(entry);
|
||||
const elements = parsed.querySelectorAll(obj.elem);
|
||||
if (elements.length > 0) {
|
||||
// pourquoi c’est ajouté même si l’élément n’existe pas ?
|
||||
elements[0].classList.add("pagedjs_full-page-specific");
|
||||
const clone = elements[0].cloneNode(true);
|
||||
obj.elemClone = clone.outerHTML;
|
||||
elements[0].remove();
|
||||
}
|
||||
this.specificPageClone.add(JSON.stringify(obj));
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
renderNode(clone, node) {
|
||||
// FULL SPREAD
|
||||
// if you find a full page element, move it in the array
|
||||
if (node.nodeType == 1 && node.classList.contains("pagedjs_full-spread-elem")) {
|
||||
this.fullSpreadEls.add(node);
|
||||
this.usedPagedEls.add(node);
|
||||
|
||||
// remove the element from the flow by hiding it.
|
||||
clone.style.display = "none";
|
||||
}
|
||||
|
||||
// FULL PAGE
|
||||
if (node.nodeType == 1 && node.classList.contains("pagedjs_full-page-left-elem")) {
|
||||
this.fullLeftEls.add(node);
|
||||
this.usedPagedEls.add(node);
|
||||
clone.style.display = "none";
|
||||
}else if (node.nodeType == 1 && node.classList.contains("pagedjs_full-page-right-elem")) {
|
||||
this.fullRightEls.add(node);
|
||||
this.usedPagedEls.add(node);
|
||||
clone.style.display = "none";
|
||||
}else if (node.nodeType == 1 && node.classList.contains("pagedjs_full-page-elem")) {
|
||||
this.fullPageEls.add(node);
|
||||
this.usedPagedEls.add(node);
|
||||
clone.style.display = "none";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
afterPageLayout(pageElement, page, breakToken, chunker) {
|
||||
|
||||
if(page.id == "page-1"){
|
||||
let allPages = document.querySelector(".pagedjs_pages");
|
||||
allPages.style.setProperty('--bleed-images', bleedFull);
|
||||
}
|
||||
|
||||
// ADD --pagedjs-fold on body if doesn't exist
|
||||
if(pageElement.classList.contains("pagedjs_first_page")){
|
||||
let body = document.getElementsByTagName("body")[0];
|
||||
let style = window.getComputedStyle(body);
|
||||
let fold = style.getPropertyValue('--pagedjs-fold');
|
||||
if(!fold){
|
||||
body.style.setProperty('--pagedjs-fold', '0mm')
|
||||
}
|
||||
}
|
||||
|
||||
// FULL SPREAD
|
||||
// if there is an element in the fullSpreadEls Set, (goodbye arrays!)
|
||||
|
||||
for (let img of this.fullSpreadEls) {
|
||||
|
||||
if (page.element.classList.contains("pagedjs_right_page")) {
|
||||
|
||||
let imgLeft;
|
||||
let imgRight;
|
||||
|
||||
if (img.nodeName == "IMG") {
|
||||
/* Add outside + inside container if the element is an img */
|
||||
let containerLeft = document.createElement("div");
|
||||
containerLeft.classList.add("pagedjs_full-spread_container");
|
||||
let containerLeftInside = document.createElement("div");
|
||||
containerLeftInside.classList.add("pagedjs_full-spread_content");
|
||||
containerLeft.appendChild(containerLeftInside).appendChild(img);
|
||||
imgLeft = containerLeft;
|
||||
|
||||
let containerRight = document.createElement("div");
|
||||
containerRight.classList.add("pagedjs_full-spread_container");
|
||||
let containerRightInside = document.createElement("div");
|
||||
containerRightInside.classList.add("pagedjs_full-spread_content");
|
||||
containerRight.appendChild(containerRightInside).appendChild(img.cloneNode(true));
|
||||
imgRight = containerRight;
|
||||
|
||||
} else {
|
||||
/* Add outside container if the element is an img */
|
||||
let containerLeft = document.createElement("div");
|
||||
containerLeft.classList.add("pagedjs_full-spread_container");
|
||||
img.classList.add("pagedjs_full-spread_content");
|
||||
containerLeft.appendChild(img);
|
||||
imgLeft = containerLeft;
|
||||
let containerRight = document.createElement("div");
|
||||
containerRight.classList.add("pagedjs_full-spread_container");
|
||||
img.classList.add("pagedjs_full-spread_content");
|
||||
containerRight.appendChild(img.cloneNode(true));
|
||||
imgRight = containerRight;
|
||||
|
||||
}
|
||||
|
||||
// put the first element on the page
|
||||
let fullPage = chunker.addPage();
|
||||
fullPage.element
|
||||
.querySelector(".pagedjs_page_content")
|
||||
.insertAdjacentElement("afterbegin", imgLeft);
|
||||
fullPage.element.classList.add("pagedjs_page_fullLeft");
|
||||
|
||||
// page right
|
||||
let fullPageRight = chunker.addPage();
|
||||
fullPageRight.element
|
||||
.querySelector(".pagedjs_page_content")
|
||||
.insertAdjacentElement("afterbegin", imgRight);
|
||||
fullPageRight.element.classList.add("pagedjs_page_fullRight");
|
||||
img.style.removeProperty("display");
|
||||
|
||||
this.fullSpreadEls.delete(img);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// FULL PAGE
|
||||
// if there is an element in the fullPageEls Set
|
||||
for (let img of this.fullPageEls) {
|
||||
let container = document.createElement("div");
|
||||
container.classList.add("pagedjs_full-page_content");
|
||||
container.appendChild(img);
|
||||
let fullPage = chunker.addPage();
|
||||
|
||||
fullPage.element
|
||||
.querySelector(".pagedjs_page_content")
|
||||
.insertAdjacentElement("afterbegin", container);
|
||||
fullPage.element.classList.add("pagedjs_page_fullPage");
|
||||
img.style.removeProperty("display");
|
||||
|
||||
this.fullPageEls.delete(img);
|
||||
}
|
||||
|
||||
// FULL Left PAGE
|
||||
// if there is an element in the fullLeftEls Set
|
||||
for (let img of this.fullLeftEls) {
|
||||
|
||||
if (page.element.classList.contains("pagedjs_right_page")) {
|
||||
let container = document.createElement("div");
|
||||
container.classList.add("pagedjs_full-page_content");
|
||||
container.appendChild(img);
|
||||
let fullPage = chunker.addPage();
|
||||
|
||||
fullPage.element
|
||||
.querySelector(".pagedjs_page_content")
|
||||
.insertAdjacentElement("afterbegin", container);
|
||||
fullPage.element.classList.add("pagedjs_page_fullPage");
|
||||
img.style.removeProperty("display");
|
||||
|
||||
this.fullLeftEls.delete(img);
|
||||
}
|
||||
}
|
||||
|
||||
// FULL RIGHT PAGE
|
||||
// if there is an element in the fullRightEls Set
|
||||
for (let img of this.fullRightEls) {
|
||||
|
||||
if (page.element.classList.contains("pagedjs_left_page")) {
|
||||
let container = document.createElement("div");
|
||||
container.classList.add("pagedjs_full-page_content");
|
||||
container.appendChild(img);
|
||||
let fullPage = chunker.addPage();
|
||||
|
||||
fullPage.element
|
||||
.querySelector(".pagedjs_page_content")
|
||||
.insertAdjacentElement("afterbegin", container);
|
||||
fullPage.element.classList.add("pagedjs_page_fullPage");
|
||||
img.style.removeProperty("display");
|
||||
|
||||
this.fullRightEls.delete(img);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// SPECIFIC PAGE ------------------------------------
|
||||
let pageNum = pageElement.id.split('page-')[1];
|
||||
pageNum = parseInt(pageNum);
|
||||
|
||||
this.specificPageClone.forEach(entry => {
|
||||
const obj = JSON.parse(entry);
|
||||
let targetedPage = obj.page;
|
||||
let prevPage = parseInt(targetedPage) - 1;
|
||||
|
||||
let elem = obj.elemClone;
|
||||
|
||||
if(prevPage == pageNum){
|
||||
let container = document.createElement("div");
|
||||
container.classList.add("pagedjs_full-page_content");
|
||||
container.innerHTML = elem;
|
||||
let fullPage = chunker.addPage();
|
||||
|
||||
fullPage.element
|
||||
.querySelector(".pagedjs_page_content")
|
||||
.insertAdjacentElement("afterbegin", container);
|
||||
fullPage.element.classList.add("pagedjs_page_fullPage");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
1
public/csspageweaver/plugins/grid/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/grid/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
7
public/csspageweaver/plugins/grid/README.md
Normal file
7
public/csspageweaver/plugins/grid/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: grid
|
||||
tags: experimental, boilerplate, gui
|
||||
description: A simple helper to see custom grid (1-12 columns)
|
||||
---
|
||||
|
||||
Caution: This plugin is not yet fully functional and requires further work.
|
||||
13
public/csspageweaver/plugins/grid/config.json
Normal file
13
public/csspageweaver/plugins/grid/config.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "Grid",
|
||||
"description": "",
|
||||
"version": "1.0",
|
||||
"ui": {
|
||||
"title": "Grid",
|
||||
"description": "This Toogle a 12 columns grid",
|
||||
"toggle": true
|
||||
},
|
||||
"stylesheet": "grid.css",
|
||||
"script": "grid-ui.js",
|
||||
"hook": "grid-hook.js"
|
||||
}
|
||||
17
public/csspageweaver/plugins/grid/grid-hook.js
Normal file
17
public/csspageweaver/plugins/grid/grid-hook.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* @name Grid
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/grid/ }
|
||||
*/
|
||||
|
||||
import { Handler } from '/csspageweaver/lib/paged.esm.js';
|
||||
|
||||
export default class GridPage extends Handler {
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
45
public/csspageweaver/plugins/grid/grid-ui.js
Normal file
45
public/csspageweaver/plugins/grid/grid-ui.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @name Grid
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/grid/ }
|
||||
*/
|
||||
|
||||
|
||||
export default function gridEvents(){
|
||||
let body = cssPageWeaver.ui.body;
|
||||
let fileTitle = cssPageWeaver.docTitle;
|
||||
|
||||
let grid = {};
|
||||
|
||||
// valeur par défaut → grille désactivée
|
||||
grid.default = {
|
||||
toggle: 'no-grid'
|
||||
};
|
||||
|
||||
grid.toggle = {};
|
||||
grid.toggle.input = cssPageWeaver.ui.grid.toggleInput;
|
||||
grid.toggle.label = cssPageWeaver.ui.grid.toggleLabel;
|
||||
|
||||
/* Récupération de la session précédente */
|
||||
grid.toggle.value = localStorage.getItem('gridToggle_' + fileTitle) || grid.default.toggle;
|
||||
|
||||
if(grid.toggle.value === "grid"){
|
||||
body.classList.add('grid'); // on indique qu'il y a la grille
|
||||
grid.toggle.input.checked = true;
|
||||
} else {
|
||||
body.classList.remove('grid'); // grille désactivée
|
||||
grid.toggle.input.checked = false;
|
||||
}
|
||||
|
||||
/* Événement toggle */
|
||||
grid.toggle.input.addEventListener("input", (e) => {
|
||||
if(e.target.checked){
|
||||
body.classList.add('grid'); // grille activée
|
||||
localStorage.setItem('gridToggle_' + fileTitle, 'grid');
|
||||
} else {
|
||||
body.classList.remove('grid'); // grille désactivée
|
||||
localStorage.setItem('gridToggle_' + fileTitle, 'no-grid');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
127
public/csspageweaver/plugins/grid/grid.css
Normal file
127
public/csspageweaver/plugins/grid/grid.css
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
@media screen{
|
||||
|
||||
|
||||
/* .pagedjs_sheet{
|
||||
--grid-bold: #888;
|
||||
--grid-light: #cfcfcf;
|
||||
background-color: #e5e5f7;
|
||||
opacity: 0.8;
|
||||
background-image: linear-gradient(90deg, var(--grid-bold) 1px, transparent 1px), linear-gradient(transparent 1px, transparent 1px), linear-gradient(90deg, var(--grid-light) 1px, white 1px);
|
||||
|
||||
background-size: 40px 40px, 40px 40px, 10px 10px, 10px 10px;
|
||||
background-position: -2px -2px, -2px -2px, -1px -1px, -1px -1px;
|
||||
} */
|
||||
|
||||
|
||||
body.grid .pagedjs_sheet {
|
||||
|
||||
|
||||
--grid-bold: #cfcfcf;
|
||||
--grid-light: #efefef;
|
||||
|
||||
--bold-step: 56px;
|
||||
--light-step: 14px;
|
||||
--shift-x: 28px;
|
||||
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
|
||||
background-image:
|
||||
/* Traits bold */
|
||||
repeating-linear-gradient(
|
||||
90deg,
|
||||
var(--grid-bold) 0 1px,
|
||||
transparent 1px var(--bold-step)
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
0deg,
|
||||
var(--grid-bold) 0 1px,
|
||||
transparent 1px var(--bold-step)
|
||||
),
|
||||
|
||||
/* Traits fins (3 traits → 4 carrés) */
|
||||
repeating-linear-gradient(
|
||||
90deg,
|
||||
var(--grid-light) 0 1px,
|
||||
transparent 1px var(--light-step)
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
0deg,
|
||||
var(--grid-light) 0 1px,
|
||||
transparent 1px var(--light-step)
|
||||
);
|
||||
|
||||
background-position:
|
||||
var(--shift-x) 0,
|
||||
var(--shift-x) 0,
|
||||
var(--shift-x) 0,
|
||||
var(--shift-x) 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.grid-page{
|
||||
--nbr-columns: 8;
|
||||
width: var(--pagedjs-pagebox-width);
|
||||
height: var(--pagedjs-pagebox-height);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--nbr-columns), calc(100%/var(--nbr-columns)));
|
||||
|
||||
box-shadow: 1px 0px 0px 0px var(--grid-color);
|
||||
|
||||
--grid-color: magenta;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.pagedjs_right_page .grid-page{
|
||||
padding-left: var(--pagedjs-margin-left);
|
||||
padding-right: var(--pagedjs-margin-right);
|
||||
}
|
||||
|
||||
.pagedjs_left_page .grid-page{
|
||||
padding-left: var(--pagedjs-margin-left);
|
||||
padding-right: var(--pagedjs-margin-right);
|
||||
}
|
||||
|
||||
|
||||
.grid-page .grid-column{
|
||||
box-shadow: 1px 0px 0px 0px var(--grid-color);
|
||||
grid-row: 1/end;
|
||||
width: 100%;
|
||||
justify-self: right;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.grid-column-0{
|
||||
grid-column: 1;
|
||||
box-shadow: -1px 0px 0px 0px var(--grid-color)!important;
|
||||
justify-self: left;
|
||||
}
|
||||
|
||||
.grid-column-1{ grid-column: 1; }
|
||||
.grid-column-2{ grid-column: 2; }
|
||||
.grid-column-3{ grid-column: 3; }
|
||||
.grid-column-4{ grid-column: 4; }
|
||||
.grid-column-5{ grid-column: 5; }
|
||||
.grid-column-6{ grid-column: 6; }
|
||||
.grid-column-7{ grid-column: 7; }
|
||||
.grid-column-8{ grid-column: 8; }
|
||||
.grid-column-9{ grid-column: 9; }
|
||||
.grid-column-10{ grid-column: 10; }
|
||||
.grid-column-11{ grid-column: 11; }
|
||||
.grid-column-12{ grid-column: 12; }
|
||||
}
|
||||
|
||||
.no-grid .grid-page{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
1
public/csspageweaver/plugins/imposition/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/imposition/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
17
public/csspageweaver/plugins/imposition/config.json
Normal file
17
public/csspageweaver/plugins/imposition/config.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "Booklet Imposition",
|
||||
"description": " This script re-arrange the pages of your document in order to make an imposed sheet layouts for printing. ",
|
||||
"author": ["Quentin Juhel", "Julien Taquet", "Julien Bidoret", "Julie Blanc"],
|
||||
"licence": "MIT",
|
||||
"repository": "https://gitlab.coko.foundation/pagedjs/pagedjs-plugins/booklet-imposition",
|
||||
"version": "1.0",
|
||||
"ui": {
|
||||
"title": "Imposition",
|
||||
"description": "Re-arrange the pages in order to make an imposed sheet layouts for printing.",
|
||||
"toggle": false,
|
||||
"template": "template.html"
|
||||
},
|
||||
"hook": "index.js"
|
||||
}
|
||||
|
||||
|
||||
363
public/csspageweaver/plugins/imposition/index.js
Normal file
363
public/csspageweaver/plugins/imposition/index.js
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
/**
|
||||
* @file Imposition for booklet(s)
|
||||
* This script re-arrange the pages of your document in order to make an imposed sheet layouts for printing.
|
||||
* Two pages per sheet, double-sided
|
||||
* @author Originally developped by Julien Bidoret,Quentin Juhel and Julien Taquet,
|
||||
* @author adapted for this project by Julie Blanc
|
||||
* @see { @link https://gitlab.coko.foundation/pagedjs/pagedjs-plugins/booklet-imposition }
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/imposition/}
|
||||
*/
|
||||
|
||||
import { Handler } from '/csspageweaver/lib/paged.esm.js';
|
||||
|
||||
export default class booklet extends Handler {
|
||||
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
this.pagedbooklet;
|
||||
this.pagedspread;
|
||||
this.sourceSize;
|
||||
this.pageStart;
|
||||
this.pageEnd;
|
||||
}
|
||||
|
||||
afterRendered(pages) {
|
||||
|
||||
/* Launch when printing */
|
||||
window.addEventListener("beforeprint", (evenement) => {
|
||||
|
||||
let format = document.querySelector(".pagedjs_page");
|
||||
|
||||
/* Width of page without bleed, extract the first number of calc() function */
|
||||
let width = getCSSCustomProp("--pagedjs-width", format);
|
||||
let numbers = width
|
||||
.match(/[0-9]+/g)
|
||||
.map(function (n) {
|
||||
return + (n);
|
||||
});
|
||||
width = parseInt(numbers[0]);
|
||||
|
||||
/* Height of page with bleed, addition of all the numbers of calc() function*/
|
||||
let height = getCSSCustomProp("--pagedjs-height", format);
|
||||
numbers = height
|
||||
.match(/[0-9]+/g)
|
||||
.map(function (n) {
|
||||
return + (n);
|
||||
});
|
||||
const reducer = (previousValue, currentValue) => previousValue + currentValue;
|
||||
height = numbers.reduce(reducer);
|
||||
|
||||
/* Bleed of the page */
|
||||
let bleed = getCSSCustomProp("--pagedjs-bleed-top", format);
|
||||
let bleedNum = parseInt(bleed);
|
||||
|
||||
/* Spread and half-spread*/
|
||||
let spread = width * 2 + bleedNum * 2;
|
||||
let spreadHalf = width + bleedNum;
|
||||
let onePage = width + bleedNum*2;
|
||||
|
||||
// get values
|
||||
let inputBooklet = document.querySelector("#input-booklet");
|
||||
let inputSpread = document.querySelector("#input-spread");
|
||||
let inputRange = document.querySelector("#imposition-range");
|
||||
let inputBookletStart = document.querySelector("#booklet-start").value;
|
||||
let inputBookletEnd = document.querySelector("#booklet-end").value;
|
||||
|
||||
let containerPages = document.querySelector(".pagedjs_pages");
|
||||
|
||||
|
||||
|
||||
if(inputBooklet.checked){
|
||||
this.pagedbooklet = true;
|
||||
this.pageStart = 1;
|
||||
this.pageEnd = pages.length;
|
||||
|
||||
if(inputRange.checked){
|
||||
this.pageStart = parseInt(inputBookletStart);
|
||||
this.pageEnd = parseInt(inputBookletEnd);
|
||||
}
|
||||
}else if(inputSpread.checked){
|
||||
this.pagedspread = true;
|
||||
this.pageStart = 1;
|
||||
this.pageEnd = pages.length;
|
||||
|
||||
if(inputRange.checked){
|
||||
this.pageStart = parseInt(inputBookletStart);
|
||||
this.pageEnd = parseInt(inputBookletEnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Delete pages we don't want*/
|
||||
pages.forEach(page => {
|
||||
let id = parseInt(page.id.replace('page-', ''));
|
||||
if(id < this.pageStart || id > this.pageEnd){
|
||||
let pageSelect = document.querySelector('#'+ page.id);
|
||||
pageSelect.remove();
|
||||
}
|
||||
});
|
||||
|
||||
/* Reset page counter */
|
||||
let reset = parseInt(this.pageStart) - 1;
|
||||
containerPages.style.counterReset = "page " + reset;
|
||||
|
||||
|
||||
|
||||
|
||||
// Add CSS to have pages in spread
|
||||
//
|
||||
// - change size of the page when printing (actually, sheet size)
|
||||
// - flex properties
|
||||
// - delete bleeds inside spread */
|
||||
|
||||
var newSize =
|
||||
`@media print{
|
||||
@page{
|
||||
size: ${spread}mm ${height}mm;
|
||||
}
|
||||
.pagedjs_pages {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
@media screen{
|
||||
.pagedjs_pages{
|
||||
max-width: calc(var(--pagedjs-width) * 2);
|
||||
}
|
||||
}
|
||||
.pagedjs_pages {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
transform: none !important;
|
||||
height: 100% !important;
|
||||
min-height: 100%;
|
||||
max-height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.pagedjs_page {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
height: 100% !important;
|
||||
|
||||
}
|
||||
|
||||
.pagedjs_sheet {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
height: 100% !important;
|
||||
}
|
||||
body{
|
||||
--pagedjs-bleed-right-left: 0mm;
|
||||
}
|
||||
.pagedjs_left_page{
|
||||
z-index: 20;
|
||||
width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width))!important;
|
||||
}
|
||||
|
||||
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.pagedjs_right_page,
|
||||
.pagedjs_right_page .pagedjs_sheet{
|
||||
width: calc(var(--pagedjs-bleed-right-right) + var(--pagedjs-pagebox-width))!important;
|
||||
}
|
||||
|
||||
.pagedjs_right_page .pagedjs_sheet{
|
||||
grid-template-columns: [bleed-left] var(--pagedjs-bleed-right-left) [sheet-center] 1fr [bleed-right] var(--pagedjs-bleed-right-right);
|
||||
}
|
||||
.pagedjs_right_page .pagedjs_bleed-left{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pagedjs_right_page .pagedjs_bleed-top .pagedjs_marks-crop:nth-child(1),
|
||||
.pagedjs_right_page .pagedjs_bleed-bottom .pagedjs_marks-crop:nth-child(1){
|
||||
width: 0!important;
|
||||
}
|
||||
.pagedjs_first_page {
|
||||
margin-left: 0;
|
||||
}
|
||||
body{
|
||||
margin: 0
|
||||
}
|
||||
.pagedjs_page:nth-of-type(even){
|
||||
break-after: always;
|
||||
}
|
||||
.pagedjs_page,
|
||||
.pagedjs_sheet{
|
||||
width: ${spreadHalf - 0.1}mm!important;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
var twoPageView = `.pagedjs_page:nth-of-type(1){
|
||||
bakground-color: red;
|
||||
margin-left: ${spreadHalf}mm!important;
|
||||
}`;
|
||||
|
||||
// Add style for the arrangement of the pages
|
||||
|
||||
if (this.pagedbooklet == true) {
|
||||
let style = document.createElement("style");
|
||||
style.textContent = newSize;
|
||||
document
|
||||
.head
|
||||
.appendChild(style);
|
||||
|
||||
var number_of_pages = document.getElementsByClassName("pagedjs_page").length;
|
||||
var pages_array = [];
|
||||
|
||||
// If the page count isn't a multiple of 4, we need to pad the array with blank
|
||||
// pages so we have the correct number of pages for a booklet.
|
||||
//
|
||||
// ex. [1, 2, 3, 4, 5, 6, 7, 8, 9, blank, blank, blank]
|
||||
|
||||
let modulo = number_of_pages % 4;
|
||||
let additional_pages = 0;
|
||||
if (modulo != 0) {
|
||||
additional_pages = 4 - modulo;
|
||||
}
|
||||
|
||||
for (i = 0; i < additional_pages; i++) {
|
||||
let added_page = document.createElement("div");
|
||||
added_page
|
||||
.classList
|
||||
.add("pagedjs_page", "added");
|
||||
added_page.id = `page-${this.pageEnd + i + 1}`;
|
||||
document
|
||||
.querySelector(".pagedjs_pages")
|
||||
.appendChild(added_page);
|
||||
}
|
||||
|
||||
// Push each page in the array
|
||||
|
||||
for (var i = number_of_pages + additional_pages; i >= 1; i--) {
|
||||
pages_array.push(i);
|
||||
}
|
||||
|
||||
|
||||
// Split the array in half
|
||||
//
|
||||
// ex. [1, 2, 3, 4, 5, 6], [7, 8, 9, blank, blank, blank]
|
||||
|
||||
var split_start = pages_array.length / 2;
|
||||
|
||||
var split_end = pages_array.length;
|
||||
|
||||
var first_array = pages_array.slice(0, split_start);
|
||||
var second_array = pages_array.slice(split_start, split_end);
|
||||
|
||||
// Reverse the second half of the array. This is the beginning of the back half
|
||||
// of the booklet (from the center fold, back to the outside last page)
|
||||
//
|
||||
// ex. [blank, blank, blank, 9, 8, 7]
|
||||
|
||||
var second_array_reversed = second_array.reverse();
|
||||
|
||||
// Zip the two arrays together in groups of 2 These will end up being each '2-up
|
||||
// side' of the final document So, the sub-array at index zero will be the first
|
||||
// side of physical page one and index 1 will be the back side. However, they
|
||||
// won't yet be in the proper order.
|
||||
//
|
||||
// ex. [[1, blank], [2, blank], [3, blank], [4, 9], [5, 8], [6, 7]]
|
||||
|
||||
var page_groups = [];
|
||||
for (var i = 0; i < first_array.length; i++) {
|
||||
page_groups[i] = [
|
||||
first_array[i], second_array_reversed[i]
|
||||
];
|
||||
}
|
||||
|
||||
// We need to reverse every other sub-array starting with the first side. This
|
||||
// is the final step of aligning our booklet pages in the order with which the
|
||||
// booklet gets printed and bound.
|
||||
//
|
||||
// ex. [[blank, 1], [2, blank], [blank, 3], [4, 9], [8, 5], [6, 7]] final_groups
|
||||
// = page_groups.each_with_index { |group, index| group.reverse! if (index %
|
||||
// 2).zero? }
|
||||
var final_groups = [];
|
||||
for (var i = 0; i < page_groups.length; i++) {
|
||||
var group = page_groups[i];
|
||||
if (i % 2 != 0) {
|
||||
final_groups[i] = page_groups[i].reverse();
|
||||
} else {
|
||||
final_groups[i] = page_groups[i];
|
||||
}
|
||||
}
|
||||
console.log("Final Imposition Order: " + final_groups);
|
||||
|
||||
var allPages = document.querySelectorAll(".pagedjs_page");
|
||||
|
||||
var final_flat = final_groups.flat();
|
||||
|
||||
final_flat.forEach((folio, i) => {
|
||||
folio = folio + reset;
|
||||
document.querySelector(`#page-${folio}`).style.order = i;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (this.pagedspread == true) {
|
||||
console.log("double page view please");
|
||||
let style = document.createElement("style");
|
||||
style.textContent = newSize;
|
||||
document
|
||||
.head
|
||||
.appendChild(style);
|
||||
let styleBis = document.createElement("style");
|
||||
styleBis.textContent = twoPageView;
|
||||
document
|
||||
.head
|
||||
.appendChild(styleBis);
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pass in an element and its CSS Custom Property that you want the value of.
|
||||
* Optionally, you can determine what datatype you get back.
|
||||
*
|
||||
* @param {String} propKey
|
||||
* @param {HTMLELement} element=document.documentElement
|
||||
* @param {String} castAs='string'
|
||||
* @returns {*}
|
||||
*/
|
||||
const getCSSCustomProp = (
|
||||
propKey,
|
||||
element = document.documentElement,
|
||||
castAs = "string"
|
||||
) => {
|
||||
let response = getComputedStyle(element).getPropertyValue(propKey);
|
||||
|
||||
// Tidy up the string if there's something to work with
|
||||
if (response.length) {
|
||||
response = response
|
||||
.replace(/\'|"/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Convert the response into a whatever type we wanted
|
||||
switch (castAs) {
|
||||
case "number":
|
||||
case "int":
|
||||
return parseInt(response, 10);
|
||||
case "float":
|
||||
return parseFloat(response, 10);
|
||||
case "boolean":
|
||||
case "bool":
|
||||
return response === "true" || response === "1";
|
||||
}
|
||||
|
||||
// Return the string response by default
|
||||
return response;
|
||||
};
|
||||
0
public/csspageweaver/plugins/imposition/stylesheet.css
Normal file
0
public/csspageweaver/plugins/imposition/stylesheet.css
Normal file
22
public/csspageweaver/plugins/imposition/template.html
Normal file
22
public/csspageweaver/plugins/imposition/template.html
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<input type="radio" id="input-spread" data-open-container="imposition-details" name="radio-imposition">
|
||||
<label for="input-spread" class="label-block">Two-page view</label>
|
||||
|
||||
<input type="radio" id="input-booklet" data-open-container="imposition-details" name="radio-imposition">
|
||||
<label for="input-booklet" class="label-block">Booklet <span></span></label>
|
||||
|
||||
<div id="imposition-details">
|
||||
|
||||
<div id="radio-imposition-pages">
|
||||
<input type="radio" id="imposition-all" name="radio-imposition-pages" value="all" checked />
|
||||
<label for="imposition-all" class="label-block">All pages</label>
|
||||
<input type="radio" id="imposition-range" name="radio-imposition-pages" value="booklet" />
|
||||
<label for="imposition-range" class="label-block">Range</label>
|
||||
</div>
|
||||
|
||||
<div id="booklet-sheets" class="panel-group-values">
|
||||
<p>from</p>
|
||||
<input type="number" id="booklet-start" name="booklet-start" min="1" max="1000" value="1">
|
||||
<p>to</p>
|
||||
<input type="number" id="booklet-end" name="booklet-end" min="1" max="1000" value="16">
|
||||
</div>
|
||||
</div>
|
||||
1
public/csspageweaver/plugins/inlineNotes/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/inlineNotes/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
77
public/csspageweaver/plugins/inlineNotes/README.md
Normal file
77
public/csspageweaver/plugins/inlineNotes/README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
name: InlineNotes
|
||||
tags: recommended, stable
|
||||
description: This script moves listed notes to inline elements at the place of the call.
|
||||
|
||||
---
|
||||
|
||||
# Inline notes
|
||||
|
||||
To move notes in the correct place in the page, Paged.js needs to have the note element in the flow. But in convert tools like Pandoc, it’s common to have the notes elements presented in a list with link elements in the flow pointing to the correponding note.
|
||||
|
||||
This script moves listed notes to inline elements at the place of the call.
|
||||
|
||||
|
||||
|
||||
## How to use
|
||||
|
||||
Add this folder in `csspageweaver/plugins/`.
|
||||
|
||||
Call the plugin in `csspageweaver/manifest.json`:
|
||||
|
||||
```json
|
||||
"plugins": [
|
||||
"inlineNotes",
|
||||
// other plugins ...
|
||||
],
|
||||
```
|
||||
|
||||
## Config.json
|
||||
|
||||
In `manifest.json`, you can modify/add some parameters:
|
||||
|
||||
```
|
||||
"pluginsParameters": {
|
||||
// parameters of other plugins ...
|
||||
"inlineNotes": {
|
||||
"input": ".footnote-ref",
|
||||
"containerNotes": ".footnotes"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
- `input` → CSS selector of the original call element (by default: `.footnote-ref`)
|
||||
- `containerNotes` → CSS selector of the original container of the footnote list, this container is deleted after moving notes (by default: `#footnotes`)
|
||||
- `` → Class of the new span created for the note
|
||||
|
||||
|
||||
## Exemple
|
||||
|
||||
Before :
|
||||
|
||||
```HTML
|
||||
<p>Gutenberg in 1439 was the first European to use movable type.
|
||||
Among his many contributions to printing are: the invention of
|
||||
a process for mass-producing movable type; the use of oil-based
|
||||
ink for printing books; <a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a> adjustable molds; mechanical movable type; and the use of a wooden printing press similar to the agricultural screw presses of the period.</p>
|
||||
<aside id="#footnotes">
|
||||
<hr>
|
||||
<ol>
|
||||
<li id="fn1">Soap, Sex, and Cigarettes: A Cultural History of American Advertising By Juliann Sivulka, page 5</li>
|
||||
</ol>
|
||||
</aside>
|
||||
|
||||
```
|
||||
|
||||
|
||||
After (wiht the plugin):
|
||||
|
||||
```HTML
|
||||
<p>Gutenberg in 1439 was the first European to use movable type.
|
||||
Among his many contributions to printing are: the invention of
|
||||
a process for mass-producing movable type; the use of oil-based
|
||||
ink for printing books; <span class="inline-note" data-counter-note="1">Soap, Sex, and Cigarettes: A Cultural History of American Advertising By Juliann Sivulka, page 5</span>
|
||||
adjustable molds; mechanical movable type; and the use
|
||||
of a wooden printing press similar to the agricultural
|
||||
screw presses of the period.</p>
|
||||
```
|
||||
8
public/csspageweaver/plugins/inlineNotes/config.json
Normal file
8
public/csspageweaver/plugins/inlineNotes/config.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Inline notes",
|
||||
"description": "This script moves listed notes to inline elements at the place of the call",
|
||||
"author": ["Julie Blanc"],
|
||||
"license": "MIT License",
|
||||
"version": "1.0",
|
||||
"hook": "inlineNotes.js"
|
||||
}
|
||||
140
public/csspageweaver/plugins/inlineNotes/inlineNotes.js
Normal file
140
public/csspageweaver/plugins/inlineNotes/inlineNotes.js
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* @name Inline Notes
|
||||
* @author Julien Bidoret
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/inlineNotes }
|
||||
*/
|
||||
|
||||
import { Handler } from '/csspageweaver/lib/paged.esm.js';
|
||||
|
||||
export default class inlineNotes extends Handler {
|
||||
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
this.input = cssPageWeaver.features.inlineNotes.parameters?.input || ".footnote-ref"; // ← CSS selector of the call element
|
||||
this.containerNotes = cssPageWeaver.features.inlineNotes.parameters?.containerNotes || "#footnotes"; // ← CSS selector of the container of the footnote
|
||||
this.newClass = cssPageWeaver.features.inlineNotes.parameters?.newClass || "inline-note"; // ← Class of the span create for the note
|
||||
}
|
||||
|
||||
beforeParsed(content) {
|
||||
|
||||
// ---- HUGO SPECIFIC // Correction des ids et href 'fn:' en 'fn-' ----
|
||||
// 1. Tous les éléments avec un id commençant par "fn:"
|
||||
content.querySelectorAll('[id^="fn:"]').forEach(el => {
|
||||
el.id = el.id.replace(/:/g, '-');
|
||||
});
|
||||
|
||||
// 2. Tous les éléments avec un id commençant par "fnref:"
|
||||
content.querySelectorAll('[id^="fnref:"]').forEach(el => {
|
||||
el.id = el.id.replace(/:/g, '-');
|
||||
});
|
||||
|
||||
// 3. Tous les éléments avec un href commençant par "#fn:"
|
||||
content.querySelectorAll('[href^="#fn:"]').forEach(el => {
|
||||
el.href = el.href.replace(/:/g, '-');
|
||||
});
|
||||
|
||||
// 4. Tous les éléments avec un href commençant par "#fnref:"
|
||||
content.querySelectorAll('[href^="#fnref:"]').forEach(el => {
|
||||
el.href = el.href.replace(/:/g, '-');
|
||||
});
|
||||
|
||||
// 5. (optionnel) Tous les 'for' dans labels, si jamais (pas typique)
|
||||
content.querySelectorAll('[for^="fn:"]').forEach(el => {
|
||||
el.htmlFor = el.htmlFor.replace(/:/g, '-');
|
||||
});
|
||||
|
||||
|
||||
|
||||
inlineNotesHandler({
|
||||
content: content,
|
||||
input: this.input,
|
||||
containerNotes: this.containerNotes,
|
||||
type: this.newClass
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function inlineNotesHandler(params){
|
||||
|
||||
let content = params.content;
|
||||
let input = params.input;
|
||||
let type = params.type;
|
||||
let container = params.containerNotes;
|
||||
|
||||
createNotes(content, input, type, container);
|
||||
|
||||
// let noteContainer = content.querySelector(params.containerNotes);
|
||||
// if(noteContainer){
|
||||
// noteContainer.remove();
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getBlocks(element){
|
||||
return element.querySelectorAll('div,p,blockquote,section,article,h1,h2,h3,h4,h5,h6,figure');
|
||||
}
|
||||
|
||||
// get only inline-level tags
|
||||
function unwrapBlockChildren(element) {
|
||||
let blocks = getBlocks(element);
|
||||
|
||||
blocks.forEach(block => {
|
||||
block.insertAdjacentHTML("beforebegin", block.innerHTML);
|
||||
block.remove();
|
||||
});
|
||||
let remainingblocks = getBlocks(element);
|
||||
if(remainingblocks.length) unwrapBlockChildren(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
function createNotes(content, input, type, container){
|
||||
|
||||
let containers = content.querySelectorAll(container);
|
||||
|
||||
containers.forEach(function (container, index) {
|
||||
let section = container.parentNode;
|
||||
|
||||
let calls = section.querySelectorAll(input);
|
||||
calls.forEach( (call, index) => {
|
||||
|
||||
let href = call.getAttribute('href');
|
||||
let hashIndex = href.indexOf('#');
|
||||
let selector = href.slice(hashIndex);
|
||||
|
||||
let note = section.querySelector(selector);
|
||||
if (!note) {
|
||||
console.warn('Note non trouvée pour', selector);
|
||||
return;
|
||||
}
|
||||
|
||||
let back = note.querySelector(".footnote-back");
|
||||
if(back){
|
||||
back.remove();
|
||||
}
|
||||
|
||||
|
||||
|
||||
let inline_note = document.createElement('span');
|
||||
inline_note.className = type;
|
||||
let num = index + 1;
|
||||
inline_note.dataset.counterNote = num;
|
||||
inline_note.dataset.chapter = section.id;
|
||||
|
||||
inline_note.innerHTML = unwrapBlockChildren(note).innerHTML;
|
||||
call.after(inline_note);
|
||||
|
||||
call.parentElement.removeChild(call);
|
||||
|
||||
|
||||
})
|
||||
|
||||
container.remove();
|
||||
});
|
||||
|
||||
}
|
||||
1
public/csspageweaver/plugins/marginBox/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/marginBox/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
10
public/csspageweaver/plugins/marginBox/config.json
Normal file
10
public/csspageweaver/plugins/marginBox/config.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"ui": {
|
||||
"title": "Margin boxes",
|
||||
"description": "This Toogle border around margin box",
|
||||
"toggle": true
|
||||
},
|
||||
"script": "marginBox.js",
|
||||
"stylesheet": "marginBox.css"
|
||||
}
|
||||
7
public/csspageweaver/plugins/marginBox/marginBox.css
Normal file
7
public/csspageweaver/plugins/marginBox/marginBox.css
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
:root {
|
||||
--color-marginBox: purple;
|
||||
}
|
||||
|
||||
.no-marginBoxes{
|
||||
--color-marginBox: transparent;
|
||||
}
|
||||
26
public/csspageweaver/plugins/marginBox/marginBox.js
Normal file
26
public/csspageweaver/plugins/marginBox/marginBox.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* @name Margin Box
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/marginBox }
|
||||
*/
|
||||
|
||||
export default function marginBoxEvents(){
|
||||
|
||||
let body = cssPageWeaver.ui.body
|
||||
|
||||
/* MARGIN BOXES ----------------------------------------------------------------------------------------------------
|
||||
----------------------------------------------------------------------------------------------------------------*/
|
||||
let marginButton = document.querySelector('#label-marginBox-toggle');
|
||||
|
||||
body.classList.add('no-marginBoxes');
|
||||
|
||||
cssPageWeaver.ui.marginBox.toggleInput.addEventListener("input", (e) => {
|
||||
if(e.target.checked){
|
||||
/* see baseline */
|
||||
body.classList.remove('no-marginBoxes');
|
||||
}else{
|
||||
/* hide baseline */
|
||||
body.classList.add('no-marginBoxes');
|
||||
}
|
||||
});
|
||||
}
|
||||
82
public/csspageweaver/plugins/moveElems/README.md
Normal file
82
public/csspageweaver/plugins/moveElems/README.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
name: Move Elems
|
||||
tags: stable, feedback-wanted
|
||||
description: Moves elements within the DOM structure during rendering.
|
||||
---
|
||||
|
||||
|
||||
# Move Elems (paged.js)
|
||||
|
||||
This plugin moves elements within the DOM structure during rendering. Usefull with other script like [Full page](https://gitlab.com/csspageweaver/plugins/fullPage) or [Float page](https://gitlab.com/csspageweaver/plugins/floatPage).
|
||||
|
||||
This plugin moves elements relative to their sibling elements in the DOM — either forward or backward — based on the integer value you provide.
|
||||
|
||||
You need to use [csstree.js](https://github.com/csstree/csstree) in order to transform custom properties.
|
||||
If you use CSS Page Weaver is inclued by default
|
||||
|
||||
|
||||
## How to install
|
||||
|
||||
**With CSS Page Weaver**
|
||||
|
||||
Register the `moveElems` plugin in your `manifest.json`:
|
||||
|
||||
```json
|
||||
[
|
||||
"moveElems",
|
||||
// other plugins
|
||||
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
⚠️ It’s recommended to load this plugin first to prevent any conflicts with other plugins.
|
||||
|
||||
|
||||
**Without CSS Page Weaver**
|
||||
|
||||
Include both csstree and the fullPage script in your HTML `<head>`:
|
||||
|
||||
```html
|
||||
<script src="js/csstree.min.js"></script>
|
||||
<script type="module" src="path/to/fullPage/floatPage.js"></script>
|
||||
```
|
||||
|
||||
Don’t forget to update the path to the paged.esm.js module in the import statement before using the script.
|
||||
|
||||
```js
|
||||
import { Handler } from '/path/to/paged.esm.js'
|
||||
```
|
||||
|
||||
|
||||
## How to use it
|
||||
|
||||
In your CSS, add the following custom property to the elements you want to move, using an ID or class:
|
||||
|
||||
```css
|
||||
elem{
|
||||
--x-move-elem: 2;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
In this example, the element will be moved after its second next sibling.
|
||||
You can also use negative values to move elements backward, e.g.:
|
||||
|
||||
```css
|
||||
elem{
|
||||
--x-move-elem: -3;
|
||||
}
|
||||
```
|
||||
|
||||
This would move the element before its third previous sibling.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
- [pagedjs.org](https://www.pagedjs.org/)
|
||||
- [csstree.js](https://github.com/csstree/csstree)
|
||||
|
||||
MIT licence, Julie Blanc, 2025
|
||||
|
||||
|
||||
8
public/csspageweaver/plugins/moveElems/config.json
Normal file
8
public/csspageweaver/plugins/moveElems/config.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Move Elems",
|
||||
"description": "Moves elements within the DOM structure during rendering",
|
||||
"author": ["Julie Blanc"],
|
||||
"license": "MIT License",
|
||||
"version": "1.0",
|
||||
"hook": "moveElems.js"
|
||||
}
|
||||
81
public/csspageweaver/plugins/moveElems/moveElems.js
Normal file
81
public/csspageweaver/plugins/moveElems/moveElems.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* @name Move Elems v1.0
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/moveElems }
|
||||
*/
|
||||
|
||||
import { Handler } from '/csspageweaver/lib/paged.esm.js';
|
||||
|
||||
|
||||
export class moveElems extends Handler {
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
this.selectorMoveElem = new Set();
|
||||
}
|
||||
|
||||
onDeclaration(declaration, dItem, dList, rule) {
|
||||
|
||||
if (declaration.property == "--x-move-elem") {
|
||||
let selector = csstree.generate(rule.ruleNode.prelude);
|
||||
let value = declaration.value.value
|
||||
let doubleArray = [selector, value];
|
||||
this.selectorMoveElem.add(doubleArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
afterParsed(parsed){
|
||||
|
||||
console.log("MOVE ELEMS LOADED");
|
||||
|
||||
// ADD data to move elements
|
||||
for (let item of this.selectorMoveElem) {
|
||||
let elem = parsed.querySelector(item[0]);
|
||||
if(elem){
|
||||
elem.dataset.moveImg = item[1];
|
||||
}
|
||||
}
|
||||
|
||||
moveElem(parsed);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function moveElem(parsed){
|
||||
let elems = parsed.querySelectorAll('[data-move-img]');
|
||||
for (let elem of elems) {
|
||||
let n = parseInt(elem.getAttribute('data-move-img'));
|
||||
let newPlace
|
||||
if(n < 0){
|
||||
newPlace = elem.previousSibling;
|
||||
if (newPlace.nodeType !== Node.ELEMENT_NODE) {
|
||||
newPlace = newPlace.previousSibling
|
||||
}
|
||||
n = n*-1 - 1;
|
||||
for(let i = 0; i < n; i++){
|
||||
newPlace = newPlace.previousSibling
|
||||
if (newPlace.nodeType !== Node.ELEMENT_NODE) {
|
||||
newPlace = newPlace.previousSibling
|
||||
}
|
||||
}
|
||||
}else{
|
||||
newPlace = elem.nextSibling;
|
||||
if (newPlace.nodeType !== Node.ELEMENT_NODE) {
|
||||
newPlace = newPlace.nextSibling;
|
||||
}
|
||||
for(let i = 0; i < n; i++){
|
||||
newPlace = newPlace.nextSibling;
|
||||
if (newPlace.nodeType !== Node.ELEMENT_NODE) {
|
||||
newPlace = newPlace.nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newPlace.parentNode.insertBefore(elem, newPlace);
|
||||
// do next = next.nextSibling; while(next && next.nodeType !== 1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
1
public/csspageweaver/plugins/previewPage/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/previewPage/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
19
public/csspageweaver/plugins/previewPage/config.json
Normal file
19
public/csspageweaver/plugins/previewPage/config.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "Preview",
|
||||
"description": "",
|
||||
"version": "1.0",
|
||||
"ui": {
|
||||
"toggle": false,
|
||||
"template": "template.html",
|
||||
"shortcut": [
|
||||
{
|
||||
"active": true,
|
||||
"tutorial": false,
|
||||
"keys": ["shiftKey", "W"],
|
||||
"description": "Toggle Preview"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stylesheet": "stylesheet.css",
|
||||
"script": "event.js"
|
||||
}
|
||||
52
public/csspageweaver/plugins/previewPage/event.js
Normal file
52
public/csspageweaver/plugins/previewPage/event.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @name Preview page
|
||||
* @author Julie Blanc <contact@julie-blanc.fr>
|
||||
* @see { @link https://gitlab.com/csspageweaver/plugins/previewPage }
|
||||
*/
|
||||
|
||||
export default function previewEvents(){
|
||||
const body = cssPageWeaver.ui.body
|
||||
|
||||
// set a "unique" filename based on title element, in case several books are opened
|
||||
const fileTitle = document.getElementsByTagName("title")[0].text;
|
||||
|
||||
const previewToggle = document.querySelector("#preview-toggle");
|
||||
const previewButton = document.querySelector("#button-preview");
|
||||
|
||||
// Check localStorage for user's preference and apply it
|
||||
const preference = localStorage.getItem('previewToggle' + fileTitle);
|
||||
if (preference === "preview") {
|
||||
body.classList.add('interface-preview');
|
||||
previewToggle.checked = true;
|
||||
} else {
|
||||
body.classList.remove('interface-preview');
|
||||
previewToggle.checked = false;
|
||||
}
|
||||
|
||||
function preview(){
|
||||
const isPreview = body.classList.contains('interface-preview');
|
||||
body.classList.toggle('interface-preview');
|
||||
previewToggle.checked = !isPreview;
|
||||
localStorage.setItem('previewToggle' + fileTitle, isPreview ? 'no-preview' : 'preview');
|
||||
|
||||
}
|
||||
// Toggle preview mode when the button is clicked
|
||||
previewButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
preview()
|
||||
});
|
||||
|
||||
// Add keydown listener based on configuration
|
||||
cssPageWeaver.features.previewPage.ui.shortcut.forEach( shortcut => {
|
||||
// if user do not have disable plugin
|
||||
if(shortcut.active){
|
||||
|
||||
// Get shortcut combinaison from config
|
||||
const keys = shortcut.keys
|
||||
|
||||
// CSS Page Weaver has a simple function to help you register your keyboard shortcut
|
||||
cssPageWeaver.helpers.addKeydownListener(keys, preview)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
39
public/csspageweaver/plugins/previewPage/stylesheet.css
Normal file
39
public/csspageweaver/plugins/previewPage/stylesheet.css
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* PREVIEW MODE */
|
||||
|
||||
.interface-preview {
|
||||
background-color: var(--color-preview);
|
||||
--color-pageBox: #999;
|
||||
}
|
||||
|
||||
.interface-preview .pagedjs_page{
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.interface-preview .pagedjs_right_page .pagedjs_bleed,
|
||||
.interface-preview .pagedjs_left_page .pagedjs_bleed-top,
|
||||
.interface-preview .pagedjs_left_page .pagedjs_bleed-bottom,
|
||||
.interface-preview .pagedjs_left_page .pagedjs_bleed-left{
|
||||
background-color: var(--color-preview);
|
||||
z-index:999999;
|
||||
}
|
||||
|
||||
.interface-preview .pagedjs_marks-crop,
|
||||
.interface-preview .pagedjs_marks-crop{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.interface-preview .pagedjs_pagebox {
|
||||
background: none;
|
||||
}
|
||||
|
||||
|
||||
.button-print{ display: none; }
|
||||
|
||||
.interface-preview{
|
||||
--color-marginBox: transparent;
|
||||
}
|
||||
|
||||
.interface-preview .grid-page{
|
||||
display: none;
|
||||
}
|
||||
31
public/csspageweaver/plugins/previewPage/template.html
Normal file
31
public/csspageweaver/plugins/previewPage/template.html
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<div id="buttons-preview" class="buttons-group align-right">
|
||||
|
||||
<input type="checkbox" id="preview-toggle" name="preview-toggle">
|
||||
<button id="button-preview" title="Preview">
|
||||
|
||||
<svg class="icon-preview" width="100%" height="100%" version="1.1" viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<polyline fill="none" points=" 649,137.999 675,137.999 675,155.999 661,155.999 " stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"></polyline>
|
||||
<polyline fill="none" points=" 653,155.999 649,155.999 649,141.999 " stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"></polyline>
|
||||
<polyline fill="none" points=" 661,156 653,162 653,156 " stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"></polyline>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M16,25c-4.265,0-8.301-1.807-11.367-5.088c-0.377-0.403-0.355-1.036,0.048-1.413c0.404-0.377,1.036-0.355,1.414,0.048 C8.778,21.419,12.295,23,16,23c4.763,0,9.149-2.605,11.84-7c-2.69-4.395-7.077-7-11.84-7c-4.938,0-9.472,2.801-12.13,7.493 c-0.272,0.481-0.884,0.651-1.363,0.377c-0.481-0.272-0.649-0.882-0.377-1.363C5.147,10.18,10.333,7,16,7 c5.668,0,10.853,3.18,13.87,8.507c0.173,0.306,0.173,0.68,0,0.985C26.853,21.819,21.668,25,16,25z"></path>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M16,21c-2.757,0-5-2.243-5-5s2.243-5,5-5s5,2.243,5,5S18.757,21,16,21z M16,13c-1.654,0-3,1.346-3,3s1.346,3,3,3 s3-1.346,3-3S17.654,13,16,13z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
<button title="Print" id="button-print" onclick="window.print()" data-ready="false" data-text="Print">
|
||||
<svg class="reset-this icon-printer" width="100%" height="100%" viewBox="0 0 478 478" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M460.8,119.467L375.467,119.467L375.467,17.067C375.467,7.641 367.826,0 358.4,0L119.467,0C110.041,0 102.4,7.641 102.4,17.067L102.4,119.467L17.067,119.467C7.641,119.467 0,127.108 0,136.533L0,358.4C0,367.826 7.641,375.467 17.067,375.467L102.4,375.467L102.4,460.8C102.4,470.226 110.041,477.867 119.467,477.867L358.4,477.867C367.826,477.867 375.467,470.226 375.467,460.8L375.467,375.467L460.8,375.467C470.226,375.467 477.867,367.826 477.867,358.4L477.867,136.533C477.867,127.108 470.226,119.467 460.8,119.467ZM136.533,34.133L341.333,34.133L341.333,119.466L136.533,119.466L136.533,34.133ZM341.333,443.733L136.533,443.733L136.533,290.133L341.333,290.133L341.333,443.733ZM443.733,341.333L375.466,341.333L375.466,290.133L392.533,290.133C401.959,290.133 409.6,282.492 409.6,273.066C409.6,263.64 401.959,256 392.533,256L85.333,256C75.907,256 68.266,263.641 68.266,273.067C68.266,282.493 75.907,290.134 85.333,290.134L102.4,290.134L102.4,341.334L34.133,341.334L34.133,153.6L443.733,153.6L443.733,341.333Z" style="fill-rule:nonzero;"></path>
|
||||
<path d="M409.6,187.733L392.533,187.733C383.107,187.733 375.466,195.374 375.466,204.8C375.466,214.226 383.107,221.867 392.533,221.867L409.6,221.867C419.026,221.867 426.667,214.226 426.667,204.8C426.667,195.374 419.026,187.733 409.6,187.733Z" style="fill-rule:nonzero;"></path>
|
||||
<path d="M290.133,324.267L187.733,324.267C178.307,324.267 170.666,331.908 170.666,341.334C170.666,350.76 178.307,358.401 187.733,358.401L290.133,358.401C299.559,358.401 307.2,350.76 307.2,341.334C307.2,331.908 299.559,324.267 290.133,324.267Z" style="fill-rule:nonzero;"></path>
|
||||
<path d="M290.133,375.467L187.733,375.467C178.307,375.467 170.666,383.108 170.666,392.534C170.666,401.96 178.307,409.601 187.733,409.601L290.133,409.601C299.559,409.601 307.2,401.96 307.2,392.534C307.2,383.108 299.559,375.467 290.133,375.467Z" style="fill-rule:nonzero;"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
1
public/csspageweaver/plugins/reloadInPlace/.gitignore
vendored
Normal file
1
public/csspageweaver/plugins/reloadInPlace/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
17
public/csspageweaver/plugins/reloadInPlace/Readme.md
Normal file
17
public/csspageweaver/plugins/reloadInPlace/Readme.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Reload In Place
|
||||
|
||||
## Configuration
|
||||
|
||||
In `manifest.json`
|
||||
|
||||
```json
|
||||
"plugins":{
|
||||
"inlineNotes"
|
||||
},
|
||||
"pluginsParameters":{
|
||||
"reloadInPlace": {
|
||||
"blur": false,
|
||||
"behavior": "instant"
|
||||
}
|
||||
},
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue