Initial commit

This commit is contained in:
sarahgarcin1 2026-01-05 19:33:15 +01:00
commit 388079e6bb
1108 changed files with 330121 additions and 0 deletions

21
.editorconfig Normal file
View file

@ -0,0 +1,21 @@
[*.{css,scss,less,js,json,ts,sass,html,hbs,mustache,phtml,html.twig,md,yml}]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
indent_size = 4
trim_trailing_whitespace = false
[site/templates/**.php]
indent_size = 2
[site/snippets/**.php]
indent_size = 2
[package.json,.{babelrc,editorconfig,eslintrc,lintstagedrc,stylelintrc}]
indent_style = space
indent_size = 2

55
.gitignore vendored Normal file
View file

@ -0,0 +1,55 @@
job-log.md
# System files
# ------------
Icon
.DS_Store
# Temporary files
# ---------------
/media/*
!/media/index.html
# Lock files
# ---------------
.lock
# Editors
# (sensitive workspace files)
# ---------------------------
*.sublime-workspace
/.vscode
/.idea
# -------------SECURITY-------------
# NEVER publish these files via Git!
# -------------SECURITY-------------
# Cache Files
# ---------------
/site/cache/*
!/site/cache/index.html
# Accounts
# ---------------
/site/accounts/*
!/site/accounts/index.html
# Sessions
# ---------------
/site/sessions/*
!/site/sessions/index.html
# License
# ---------------
/site/config/.license

67
.htaccess Normal file
View file

@ -0,0 +1,67 @@
# Kirby .htaccess
# revision 2023-07-22
# rewrite rules
<IfModule mod_rewrite.c>
# enable awesome urls. i.e.:
# http://yourdomain.com/about-us/team
RewriteEngine on
# make sure to set the RewriteBase correctly
# if you are running the site in a subfolder;
# otherwise links or the entire site will break.
#
# If your homepage is http://yourdomain.com/mysite,
# set the RewriteBase to:
#
# RewriteBase /mysite
# In some environments it's necessary to
# set the RewriteBase to:
#
# RewriteBase /
# block files and folders beginning with a dot, such as .git
# except for the .well-known folder, which is used for Let's Encrypt and security.txt
RewriteRule (^|/)\.(?!well-known\/) index.php [L]
# block all files in the content folder from being accessed directly
RewriteRule ^content/(.*) index.php [L]
# block all files in the site folder from being accessed directly
RewriteRule ^site/(.*) index.php [L]
# block direct access to Kirby and the Panel sources
RewriteRule ^kirby/(.*) index.php [L]
# make site links work
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) index.php [L]
</IfModule>
# pass the Authorization header to PHP
SetEnvIf Authorization "(.+)" HTTP_AUTHORIZATION=$1
# compress text file responses
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>
# set security headers in all responses
<IfModule mod_headers.c>
# serve files as plain text if the actual content type is not known
# (hardens against attacks from malicious file uploads)
Header set Content-Type "text/plain" "expr=-z %{CONTENT_TYPE}"
Header set X-Content-Type-Options "nosniff"
</IfModule>

36
README.md Normal file
View file

@ -0,0 +1,36 @@
<img src="http://getkirby.com/assets/images/github/plainkit.jpg" width="300">
**Kirby: the CMS that adapts to any project, loved by developers and editors alike.**
The Plainkit is a minimal Kirby setup with the basics you need to start a project from scratch. It is the ideal choice if you are already familiar with Kirby and want to start step-by-step.
You can learn more about Kirby at [getkirby.com](https://getkirby.com).
### Try Kirby for free
You can try Kirby and the Plainkit on your local machine or on a test server as long as you need to make sure it is the right tool for your next project. … and when youre convinced, [buy your license](https://getkirby.com/buy).
### Get going
Read our guide on [how to get started with Kirby](https://getkirby.com/docs/guide/quickstart).
You can [download the latest version](https://github.com/getkirby/plainkit/archive/main.zip) of the Plainkit.
If you are familiar with Git, you can clone Kirby's Plainkit repository from Github.
git clone https://github.com/getkirby/plainkit.git
## What's Kirby?
- **[getkirby.com](https://getkirby.com)** Get to know the CMS.
- **[Try it](https://getkirby.com/try)** Take a test ride with our online demo. Or download one of our kits to get started.
- **[Documentation](https://getkirby.com/docs/guide)** Read the official guide, reference and cookbook recipes.
- **[Issues](https://github.com/getkirby/kirby/issues)** Report bugs and other problems.
- **[Feedback](https://feedback.getkirby.com)** You have an idea for Kirby? Share it.
- **[Forum](https://forum.getkirby.com)** Whenever you get stuck, don't hesitate to reach out for questions and support.
- **[Discord](https://chat.getkirby.com)** Hang out and meet the community.
- **[Mastodon](https://mastodon.social/@getkirby)** Spread the word.
- **[Bluesky](https://bsky.app/profile/getkirby.com)** Spread the word.
---
© 2009 Bastian Allgeier
[getkirby.com](https://getkirby.com) · [License agreement](https://getkirby.com/license)

File diff suppressed because it is too large Load diff

1
assets/css/libs/glightbox.min.css vendored Normal file

File diff suppressed because one or more lines are too long

267
assets/css/main.css Normal file
View file

@ -0,0 +1,267 @@
/* MOBILE FIRST */
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
}
body{
font-family: var(--fontFamily);
font-size: var(--textSize);
line-height: 1.3;
}
a{
text-decoration: none;
color: #000;
}
a:hover{text-decoration: underline;}
p{ margin-bottom: 1rem;}
img{ width: 100%;}
main{ margin-bottom: 150px; }
h1{
font-weight: normal;
font-size: 3em;
margin-top: 0;
text-transform: uppercase;
margin-bottom: 0;
string-set: title content(text);
}
h2{
string-set: chapter content(text);
text-align: center;
}
h2, h3{
font-size: 3em;
font-weight: normal;
text-transform: uppercase;
}
h3{
font-size: 1em;
}
h2 + h3{
margin-top: 0.5em;
opacity: 0.8;
font-weight: normal;
}
h5, h6{
font-size: var(--textSize);
margin-top: 1mm;
margin-bottom: 0;
font-weight: normal;
}
p, li{
text-align: justify;
hyphens: auto;
line-height: 1.45;
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
-webkit-hyphenate-limit-before: 3;
-webkit-hyphenate-limit-after: 4;
-ms-hyphenate-limit-chars: 7 3 4;
hyphenate-limit-chars: 7 3 4;
word-wrap: break-word;
overflow-wrap: break-word;
/* veuves et orphelines (bancal) */
widows: 3;
orphans: 3;
}
li p{
margin: 0;
text-align: left;
}
p a {
white-space: pre-wrap; /* CSS3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
word-break: break-all;
}
ul, figure, ol{
margin: 0;
padding: 0
}
figure{
margin-bottom: 1em;
}
figcaption{
font-size: 0.8em;
margin-top: 0.5em;
}
figcaption p{
margin: 0;
}
figure[data-ratio] {
position: relative;
width: 100%;
aspect-ratio: var(--ratio);
}
figure[data-crop="true"] img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
figure[data-crop="false"] img {
width: 100%;
height: auto;
object-fit: contain;
}
.pagedjs_page_content table{
column-width: auto!important;
}
table {
border-collapse: collapse;
width: 100%;
border: 1px solid grey;
font-size: 12px;
table-layout: fixed;
margin-top: 2em;
}
th {
color: var(--green);
font-weight: bold;
text-align: left;
border-bottom: 1px solid grey;
}
th, td {
padding: 12px 8px;
padding-bottom: 12px !important;
border-right: 1px solid lightgrey;
}
tr{
border-bottom: 1px solid lightgrey;
}
tr:last-child{
border: none;
}
tr > th:first-child,
tr > th:nth-child(2){
width:20%;
}
tr > td:first-child,
tr > td:nth-child(2){
width:20%;
}
td:last-child{
display: none;
}
/* fade entre les pages */
#fade{
opacity: 0;
transition: opacity 1s ease-out;
}
#fade.loaded{
opacity: 1;
}
.glightbox-clean .gslide-title{
font-family: var(--fontFamily);
font-weight: 600;
line-height: 1.2;
}
.show-for-small-only{
display: block;
}
.hide-for-small-only{
display: none;
}
/* ---- H E A D E R -----*/
header{
position: fixed;
width: 100%;
top:0;
left:0;
right: 0;
padding: 15px;
z-index: 1000;
background: #FFF;
}
header.row{
margin:0;
}
header .mobile__menu_btn {
display: block;
position: absolute;
right: 15px;
top: 15px;
cursor: pointer;
height: 24px;
width: 24px;
}
header .mobile__menu_btn span {
display: block;
background: #000;
height: 2px;
width: 26px;
position: absolute;
transition: all 0.5s ease;
}
header .mobile__menu_btn span:nth-child(1) { top: 0px; }
header .mobile__menu_btn span:nth-child(2) { top: 8px; }
header .mobile__menu_btn span:nth-child(3) { top: 16px; }
header .mobile__menu_btn .transparent { opacity: 0; }
header .mobile__menu_btn .rotate-top {
-ms-transform: rotate(45deg);
transform: rotatez(45deg);
-webkit-transform-origin: 0%;
left: 2px;
}
header .mobile__menu_btn .rotate-bottom {
-ms-transform: rotate(-45deg);
transform: rotatez(-45deg);
-webkit-transform-origin: 15%;
}

View file

@ -0,0 +1,452 @@
/* -------- styles des images -------- s */
figure img.fill{ object-fit: fill; width:100%; height:100%;}
figure img.contain{ object-fit: contain; max-width: none; width:100%; height:100%;}
figure img.cover{ object-fit: cover; max-width: none; width:100%; height:100%;}
/* -------- sauts de pages -------- */
/* Class pour lajout d'une page */
.page{
page-break-after: always;
}
/* Classe pour l'ajout d'un saut de page */
.pagebreak{
page-break-after: always;
}
/* Classe pour l'ajout d'une page blanche */
.blank-page{
page-break-after: always;
page: blank;
}
@page blank{
background: #FFF;
@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;}
}
/* --------- Alignment for blocks ------- */
.chapter .content div.alignment-left,
.chapter .content div.alignment-left p{
text-align: left !important;
hyphens: none !important;
}
.chapter .content div.alignment-right,
.chapter .content div.alignment-right p{
text-align: right !important;
hyphens: none !important;
}
.chapter .content div.alignment-center,
.chapter .content div.alignment-center p{
text-align: center !important;
hyphens: none !important;
}
.chapter .content div.alignment-justify,
.chapter .content div.alignment-justify p{
text-align: justify !important;
hyphens: auto !important;
}
/* -------- BACKGROUND IMAGE --------- */
.spread-image {
--pagedjs-full-page: spread;
}
.full-bleed-image.full-page{
top: calc(var(--pagedjs-margin-top)*-1 - var(--pagedjs-bleed-top));
height: var(--pagedjs-height);
}
.pagedjs_left_page .full-bleed-image.full-page{
left: calc(var(--pagedjs-margin-left)*-1 - var(--pagedjs-bleed-left));
right: calc(var(--pagedjs-margin-right)*-1);
}
.pagedjs_right_page .full-bleed-image.full-page{
left: calc(var(--pagedjs-margin-left)*-1);
right: calc(var(--pagedjs-margin-right)*-1 - var(--pagedjs-bleed-right));
}
.spread-interval-image-left{
break-before: left;
page: spreadinterval;
}
.spread-interval-image-right{
page: spreadinterval;
position: relative;
}
.spread-interval-image-left figure,
.spread-interval-image-right figure{
width:200%;
}
.spread-interval-image-right figure{
margin-left: -100%;
}
.spread-interval-image-right .chapter-title-wrapper{
position: absolute;
bottom: 20mm;
right: 20mm;
}
.spread-interval-image-right .chapter-title-wrapper h1,
.spread-interval-image-right .chapter-title-wrapper .chapter-number{
color: #FFF;
border-color: #FFF;
vertical-align: middle;
}
.spread-interval-image-right .chapter-title-spanish{
margin-left: 10mm;
}
@page spreadinterval{
background: var(--backgroundImage);
margin:0;
@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;}
}
.pagedjs_spreadinterval_page .chapter{
margin-top: 0 !important;
}
.interval-image{
break-before: left;
page: interval;
}
@page interval{
background: var(--backgroundImage);
margin:0;
@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;}
}
.interval-image figcaption{
background: rgba(255, 255, 255, 1);
border-radius: 10px;
position: absolute;
margin-top: 0;
padding: 1mm 3mm;
color: var(--green);
border: 1px solid var(--green);
max-width: calc(var(--pagedjs-width) - 30mm);
bottom: 8mm;
left: 15mm;
}
.pagedjs_interval_page .chapter{
margin-top: 0 !important;
}
.spread-image,
.full-bleed-image.full-page {
height: var(--pagedjs-height);
top: auto;
display: flex;
align-items: center;
justify-content: center;
}
.spread-image figure,
.full-bleed-image figure{
height: calc(var(--pagedjs-height) + var(--pagedjs-margin-top) + var(--pagedjs-margin-bottom));
}
.spread-image figure.full-width,
.full-bleed-image figure.full-width{
height: auto;
width: calc(100% - 12mm);
margin: 6mm;
}
.spread-image figure.full-width img;
.full-bleed-image figure.full-width img{
max-width: 100%;
height: auto;
}
.full-bleed-image figure img{
object-fit: cover;
max-width: none;
width:100%;
height:100%;
}
.spread-image figure img{
object-fit: cover;
max-width: none;
width:100%;
height:100%;
}
.full-bleed-image figcaption,
.spread-image figcaption{
background: rgba(255, 255, 255, 1);
border-radius: 10px;
position: absolute;
margin-top: 0;
padding: 1mm 3mm;
color: var(--green);
border: 1px solid var(--green);
max-width: calc(var(--pagedjs-width) - 30mm);
}
.pagedjs_left_page .full-bleed-image figcaption {
left: 8mm;
}
.pagedjs_right_page .full-bleed-image figcaption{
right: 8mm;
}
.full-bleed-image figcaption {
bottom: -7mm;
}
.spread-image figcaption{
bottom: 15mm;
left: 15mm;
}
.background-image{
page: backgroundImage;
/* page-break-after: always;*/
margin-top: -1cm;
position: relative;
}
.background-image figcaption{
background: rgba(255, 255, 255, 0.9);
position: absolute;
margin-top: 0;
padding: 1mm 1mm;
top: 0;
/* left: 10mm;*/
/* top: calc(9in - 15mm);*/
}
.background-image figcaption p{
margin:0;
}
.pagedjs_page.pagedjs_right_page .background-image figure{
position: absolute;
top: calc(var(--pagedjs-bleed-top) * -1);
left:0;
width: calc(var(--pagedjs-width-right) - var(--pagedjs-bleed-right));
height: calc(var(--pagedjs-height-right));
}
.pagedjs_page.pagedjs_left_page .background-image figure{
position: absolute;
top: calc(var(--pagedjs-bleed-top) * -1);
left: calc(var(--pagedjs-bleed-left) * -1);
width: calc(var(--pagedjs-width-left) - var(--pagedjs-bleed-left));
height: var(--pagedjs-height-left);
}
@page backgroundImage{
margin: 0;
@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;}
}
/* -------- FULL PAGE --------- */
.full-page{
position: absolute;
}
.full-page.bottom{
bottom: 0;
transform: none;
top: inherit;
}
/* ------ Colonnes de texte ------ */
.col-2{
columns: 2;
column-gap: 0.5cm;
}
.col-3{
columns: 3;
column-gap: 0.5cm;
}
/* ------- IMAGES --------- */
.grid {
display: grid;
grid-template-columns: repeat(12,1fr);
grid-gap: 0.5cm;
align-content: start;
}
.grid figure {
grid-column: span 3;
align-self: start;
justify-self: start;
margin: 0;
padding: 0;
}
/* largeur des .figure */
figure.offset2 { grid-column: 3 / span 3; }
figure.offset4 { grid-column: 5 / span 3; }
figure.offset6 { grid-column: 7 / span 3; }
figure.offset8 { grid-column: 9 / span 3; }
figure.third { grid-column: span 4; }
figure.third.offset2 { grid-column: 3 / span 4; }
figure.third.offset4 { grid-column: 5 / span 4; }
figure.third.offset6 { grid-column: 7 / span 4; }
figure.third.offset8 { grid-column: 9 / span 4; }
figure.half { grid-column: span 6; }
figure.half.offset2 { grid-column: 3 / span 6; }
figure.half.offset4 { grid-column: 5 / span 6; }
figure.half.offset6 { grid-column: 7 / span 6; }
figure.twothird { grid-column: span 8; }
figure.twothird.offset2 { grid-column: 3 / span 8; }
figure.twothird.offset4 { grid-column: 5 / span 8; }
figure.threequarter { grid-column: span 9; }
figure.threequarter.offset2 { grid-column: 3 / span 9; }
figure.full { grid-column: 1 / span 12; }
/* alignement vertical des figure */
figure.top { align-self: start;}
figure.center { align-self: center;}
figure.bottom { align-self: end;}
/* ------- TABLE OF CONTENTS --------- */
.toc{
break-before: right;
}
/* counters */
#list-toc-generated{
counter-reset: counterTocLevel1;
list-style: none;
overflow-x: visible !important;
}
#list-toc-generated .toc-element-level-1{
counter-increment: counterTocLevel1;
counter-reset: counterTocLevel2;
}
#list-toc-generated a{
text-decoration: none;
}
#list-toc-generated .toc-element a::after{
content: target-counter(attr(href), page);
position: absolute;
right:0;
font-size: 13pt;
text-align: right;
}
#list-toc-generated .toc-element-level-2 a::after{
font-size: 10pt;
}
#list-toc-generated .toc-element-level-1{
font-size: 13pt;
}
#list-toc-generated .toc-element-level-2{
font-weight: bold;
margin-bottom: 20px;
}
#list-toc-generated li span{
display: block;
}
/* -------- FOOTNOTES ---------- */
span.footnote {
float: footnote;
}
.footnote::footnote-call {
content: counter(footnote, decimal) " ";
}

54
assets/css/print.css Normal file
View file

@ -0,0 +1,54 @@
@media print{
/* --------- Paramètres de base ---------- */
@page{
size: 165mm 240mm
/* bleed: 6mm;
marks: crop;*/
}
@page :left {
margin: 16mm 25mm 10mm 10mm;
@top-left-corner {
content: counter(page);
font-size: 7pt;
text-align: center;
font-family: var(--fontFamily);
}
@top-center{
content: string(title);
font-size: 7pt;
text-align: center;
text-transform: uppercase;
width: 105%;
margin-left: 0%;
}
}
@page :right {
margin: 16mm 10mm 10mm 25mm;
@top-right-corner {
content: counter(page);
font-size: 7pt;
text-align: center;
font-family: var(--fontFamily);
}
@top-center{
content: string(chapter);
font-size: 7pt;
text-align: center;
text-transform: uppercase;
width: 105%;
margin-left: -9%;
}
}
.chapter{
break-before: right;
}
.chapter h2{
page-break-after: always;
}
}

5
assets/css/reset.css Normal file
View file

@ -0,0 +1,5 @@
html {box-sizing: border-box;}*,*::before,*::after {box-sizing: inherit;}body,h1,h2,h3,h4,h5,h6,ul,ol,li,p,pre,blockquote,figure,hr {margin: 0;padding: 0; font-size:100%; font-weight: normal;}ul {list-style: none;}input,textarea,select,button {color: inherit;font: inherit;letter-spacing: inherit;}input,textarea,button {border: 1px solid gray;}button {border-radius: 0;padding: 0.75em 1em;background-color: transparent;}button * {pointer-events: none;}embed,iframe,img,object,video {display: block;max-width: 100%;}table {table-layout: fixed;width: 100%;}[hidden] {display: none !important;}noscript {display: block;margin-bottom: 1em;margin-top: 1em;}
/* Buttons and input buttons */
[role="button"],input[type="submit"],input[type="reset"],input[type="button"],button { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box;}
input[type="submit"], input[type="reset"], input[type="button"], button { background: none; border: 0; color: inherit; font: inherit; line-height: normal; overflow: visible; padding: 0; -webkit-appearance: button; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; }
input::-moz-focus-inner, button::-moz-focus-inner { border: 0; padding: 0;}

45
assets/css/responsive.css Normal file
View file

@ -0,0 +1,45 @@
/* sm size */
@media only screen and (min-width: 48em) {
.show-for-small-only{ display: none;}
.hide-for-small-only{ display: block;}
header{
padding: 15px 30px;
padding-right: 0px;
}
header .mobile__menu_btn {
display: none;
}
header nav {
height: auto;
min-height: auto;
max-height: none;
}
}
/* md size */
@media only screen and (min-width: 64em) {
header nav ul.row{
margin-left: 30px;
margin-right: 0;
}
}
/* lg size */
@media only screen and (min-width: 75em) {
}
/* xl size */
@media only screen and (min-width: 90em) {
:root{
--textSize: 20px;
}
body{
line-height: 1.4;
}
}

8
assets/css/variables.css Normal file
View file

@ -0,0 +1,8 @@
:root{
--margin: 1em;
--fontFamily: sans-serif;
--textSize: 17px;
--ratio : auto;
}

8
assets/css/web.css Normal file
View file

@ -0,0 +1,8 @@
@media screen{
main{
padding-top: 250px;
max-width: 42em;
margin: auto;
}
}

1
assets/csspageweaver/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.DS_Store

View 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'

View file

@ -0,0 +1,177 @@
# CSS Page Weaver
![Interface](https://gitlab.com/csspageweaver/csspageweaver/-/wikis/uploads/e724d6782600c30bc1b8d1ad56b24217/_screen_copie.webp)
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/)

View 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;
}

View file

@ -0,0 +1,136 @@
/* 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-margin-interface: 20px;
--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);
padding-left: var(--pagedjs-margin-interface);
padding-right: var(--pagedjs-margin-interface);
min-width: calc(var(--pagedjs-width) * 2 + var(--pagedjs-margin-interface)*2);
}
body.no-spread{
min-width: calc(var(--pagedjs-width) + var(--pagedjs-margin-interface)*2);
}
.pagedjs_pages {
display: flex;
width: calc(var(--pagedjs-width) * 2);
flex: 0;
flex-wrap: wrap;
margin: 0 auto;
margin-bottom: var(--pagedjs-header-height);
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;
}
}

View file

@ -0,0 +1,532 @@
@import url("../fonts/IBM_Plex_Mono/stylesheet.css");
@media print{
csspageweaver-gui{
display: none;
}
}
@media screen, pagedjs-ignore {
/* TOGGLEPANEL ----------------------------------- */
#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;
}
/* INPUTNUMBER ---------------------------------- */
#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;
}
/* INPUTCHECKBOX ---------------------------------- */
#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);
}
/* INPUTRADIO ---------------------------------- */
#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; }
/* CHECKEDBUTTONS --------------------------------------- */
#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);
}
/* TOGGLELABEL 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);
}
/* HOVERBUTTONS ------------------------------------------------------------ */
#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;
}

View file

@ -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;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,119 @@
/**
* @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){
document.addEventListener("DOMContentLoaded", (event) => {
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)
})
}
// 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 = `/assets/csspageweaver/interface/css/interface.css`
cssPageWeaver_frame.css = ['/assets/css/main.css']
document.body.insertAdjacentElement('afterbegin', cssPageWeaver_frame)
} else {
window.cssPageWeaver_simpleRender = new CssPageWeaver_SimpleRender()
cssPageWeaver_simpleRender.interface = `/assets/csspageweaver/interface/css/interface.css`
cssPageWeaver_simpleRender.hook = []
cssPageWeaver_simpleRender.css.push('/assets/css/main.css')
cssPageWeaver_simpleRender.setup()
}
}

View file

@ -0,0 +1,24 @@
{
"plugins": ["baseline","grid","marginBox","imposition","spread","previewPage","reloadInPlace","fullPage", "createIndex", "footnotesFix", "regexTypo", "marginNotes"],
"pluginsParameters":{
"footnotesFix": {
"selector": ".footnote",
"reset": ".chapter"
},
"marginNotes": {
"selector": ".sidenote",
"position": "outside",
"reset": ".chapter",
"marker": ". "
}
},
"css": [
"assets/css/reset.css",
"assets/fonts/stylesheet.css",
"assets/css/variables.css",
"assets/css/main.css",
"assets/css/style-template.css",
"assets/css/print.css"
]
}

View file

@ -0,0 +1,334 @@
/**
* @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 = `/assets/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 featureDir = `assets/csspageweaver//${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

View 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

View 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

View 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

View 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

View file

@ -0,0 +1 @@
# Hi! Just need to ensure folder remain tracked, even empty.

View file

@ -0,0 +1 @@
.DS_Store

View file

@ -0,0 +1,8 @@
---
name: baseline
tags: recommended, stable, boilerplate, gui
description: A simple helper to see custom baseline grid.
---

View 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;
}
}

View 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);
});
}

View 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"
}

View 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>

View file

@ -0,0 +1 @@
.DS_Store

View file

@ -0,0 +1,60 @@
# Beau Drapeau
A interface to manually but easily handle line break and letterspacing of textuals elements, in the browser.
## How to install the plugin
Add this folder to `csspageweaver/plugins/`.
Call the plugin in `csspageweaver/manifest.json`:
```json
"plugins": [
"beauDrapeau",
// other plugins ...
],
```
## Configuration
In `manifest.json`, you can modify/add some parameters:
```json
"plugins":{
"beauDrapeau"
},
"pluginsParameters":{
"beauDrapeau": {
"data": "/csspageweaver/plugins/beauDrapeau/beauDrapeau-data.js"
}
},
```
## How to use it
Edits abilities are enable with a dumb on screen ON / Off Button.
When active, two actions can be performs :
+ Add forced lines breaks in text element. Just add it with `Shift` + `Enter`.
+ Increase or decrease element letter spacing. Just hover a element press `Shift` + Scroll Wheel
### Retrieve changes
When disable, script download an data file named `_beaudrapeau.js`. Placed on page folder, it'll allow script to retrieve current change on next session.
### Reset
Enable edition, mouse over an element and press `Shift` + `R`
## Script behavior
+ `beauDrapeau-hook.js` will extend `PagedJs` class
+ retrieve eventuals previous edits as a `_beaudrapeau.js` file in page folder
+ act before PagedJs parse content.
## Licence
The MIT License (MIT)

View file

@ -0,0 +1,16 @@
[
{
"id": "c1",
"breaking": [
415
],
"letterspacing": "0.25em"
},
{
"id": "c2",
"breaking": [
574
],
"letterspacing": null
}
]

View file

@ -0,0 +1,130 @@
/**
* @name Beau drapeau
* @author Benjamin G. <ecrire@bnjm.eu>
* @see { @link https://gitlab.com/csspageweaver/plugins/beauDrapeau }
*/
import { Handler } from 'assets/csspageweaver/lib/paged.esm.js';
import { beauDrapeau } from '/csspageweaver/plugins/beauDrapeau/beauDrapeau.js';
/* */
export class PrepareBeauDrapeau extends Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
this.parameters = cssPageWeaver.features.beauDrapeau || {}
this.parameters.data = cssPageWeaver.features.beauDrapeau?.data || cssPageWeaver.features.beauDrapeau.directory + 'beauDrapeau-data.js'
}
addClassToBreaking(content){
let brs = content.querySelectorAll("br")
brs.forEach(br => {
br.classList.add("canon")
})
}
getPageAssetsLocation(){
const pathname = window.location.pathname
const index = pathname.lastIndexOf('/')
const filename = pathname.substring(index + 1)
const dotIndex = filename.lastIndexOf('.')
const pagename = filename.substring(0, dotIndex)
return pagename
}
assignEditableCapabilities(element) {
element.setAttribute("contentEditable", false)
}
assignEditableIds(content) {
const sections = content.querySelectorAll('section');
let sectionCounter = 0;
let idCounter = 0;
let idPrefix = 'a';
sections.forEach((section) => {
idCounter = 0;
idPrefix = String.fromCharCode(sectionCounter + 97);
const selectors = '*:not(hgroup) p, *:not(hgroup) li, *:not(hgroup) h1, *:not(hgroup) h2, *:not(hgroup) h3, *:not(hgroup) h4, *:not(hgroup) h5, *:not(hgroup) h6'
const targetElements = section.querySelectorAll(selectors);
targetElements.forEach((element) => {
idCounter++;
const editableId = `${idPrefix}${idCounter}`;
element.setAttribute('editable-id', editableId);
this.assignEditableCapabilities(element)
});
sectionCounter++;
});
}
async applyBackUp(content){
console.log("retrieve previous beauDrapeau", beauDrapeau.data )
// Loop through each object in the beauDrapeauData array
beauDrapeau.data.forEach((data) => {
// Get the corresponding DOM element by ID
const element = content.querySelector(`[editable-id="${data.id}"`);
if (element) {
// If the element exists
// and has a non-null breaking array, add <br> elements
if (data.breaking && data.breaking.length > 0) {
let textContent = element.textContent;
data.breaking.forEach((index) => {
const firstPart = textContent.slice(0, index);
const secondPart = textContent.slice(index);
element.innerHTML = `${firstPart}<br>${secondPart}`;
textContent = element.textContent; // Update the textContent variable to reflect the new <br> element
});
}
// If the element exists
// and has a non-null letterspacing value, add inline styles
if (data.letterspacing !== null) {
element.style.letterSpacing = `${data.letterspacing}`;
}
}
});
}
// new function to load _beauDrapeau.json
async loadBackUp() {
try {
let data_location = this.parameters.data ? this.parameters.data : `./${this.getPageAssetsLocation()}/beauDrapeau-data.js`
const response = await fetch(data_location);
//const response = await fetch(`./${cssPageWeaver.directory.plugins}/beauDrapeau/beauDrapeau-data.js`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data
} catch (error) {
}
}
async beforeParsed(content) {
// Canonise existing Breaking Point
this.addClassToBreaking(content)
// Assign ID to all editable element
this.assignEditableIds(content)
// Load previous data
beauDrapeau.data = await this.loadBackUp();
// Break if no data
if(!beauDrapeau.data){ return }
await this.applyBackUp(content)
}
}

View file

@ -0,0 +1,23 @@
/**
* @name Beau drapeau
* @author Benjamin G. <ecrire@bnjm.eu>
* @see { @link https://gitlab.com/csspageweaver/plugins/beauDrapeau }
*/
import { beauDrapeau } from '/csspageweaver/plugins/beauDrapeau/beauDrapeau.js';
export default function () {
// Declare selector
let toggleInput = cssPageWeaver.ui.beauDrapeau.toggleInput
// Add event
toggleInput.addEventListener('input', () => {
// Change toggle button style and content
//beauDrapeau().ui().toggle().button('edit-button')
// Run function
beauDrapeau().edit()
})
}

View file

@ -0,0 +1,335 @@
/**
* @file Manage text alignement
* A interface to manually but easily handle line break and letterspacing of textuals elements, in the browser.
* @author Benjamin G. <ecrire@bnjm.eu>
* @see https://gitlab.com/BenjmnG/beauDrapeau
*/
export const beauDrapeau = () => ({
isEditMode: false,
data: new Array(),
ui: () => ({
toggle: () => ({
button: (id) => {
var button = document.getElementById(id);
if(!button){
return
}
// Toggle button value
if (beauDrapeau.isEditMode) {
button.classList.add('active')
button.value = 'Edit On';
} else {
button.classList.remove('active')
button.value = 'Edit Off';
}
},
/**
* Toggles the `contentEditable` attribute for all elements with the `contentEditable` attribute.
*/
contentEditable: () => {
const editableElements = document.querySelectorAll('[contentEditable]');
editableElements.forEach((element) => {
element.contentEditable = beauDrapeau.isEditMode;
});
},
}),
download: (dataBlob) => {
// Check if the download link already exists
const existingLink = document.getElementById('edit-download');
if (existingLink) {
// If the link exists, update the URL and text content
existingLink.href = URL.createObjectURL(dataBlob);
} else {
// If the link doesn't exist, create a new one and append it to the document body
let link = document.createElement('a');
link.id = 'edit-download';
link.classList = 'button';
link.href = URL.createObjectURL(dataBlob);
link.download = 'beauDrapeau-data.js';
link.innerHTML = '<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 0 40 40"> <defs> <style> .a8r7{ fill: none; stroke: currentColor; stroke-miterlimit: 10; stroke-width: 3px; stroke-linecap: round;} </style> </defs> <line class="a8r7" x1="15.49" x2="15.49" y2="27.93"/> <line class="a8r7" y1="34.77" x2="31.31" y2="34.77"/> <polyline class="a8r7" points="22.9 21.02 15.65 28.26 8.41 21.02"/> </svg>'
let div = document.createElement('div');
div.classList = "buttons-group"
div.append(link)
console.log(div)
// Add styles
//link = beauDrapeau().ui().style(link, 1)
document.getElementById('beauDrapeau-form').insertAdjacentElement('beforeEnd', div);
}
},
event: () => ({
letterSpacing: () => {
let letterSpacing = 0;
const handleScroll = (event) => {
if (event.shiftKey && event.target.isContentEditable) {
event.preventDefault();
const step = 0.01;
const newLetterSpacing = event.deltaY > 0 ? letterSpacing - step : letterSpacing + step;
event.target.style.letterSpacing = `${newLetterSpacing}em`;
letterSpacing = newLetterSpacing;
}
};
const handleMouseOver = (event) => {
if (event.target.isContentEditable) {
event.target.addEventListener('wheel', handleScroll);
event.target.addEventListener('mouseleave', handleMouseOut);
}
};
const handleMouseOut = (event) => {
if (event.target.isContentEditable) {
event.target.removeEventListener('wheel', handleScroll);
event.target.removeEventListener('mouseleave', handleMouseOut);
}
};
if (beauDrapeau.isEditMode) {
document.addEventListener('mouseover', handleMouseOver);
document.addEventListener('mouseout', handleMouseOut);
} else {
document.removeEventListener('mouseover', handleMouseOver);
document.removeEventListener('mouseout', handleMouseOut);
}
},
reset: () => {
const handleKeyDown = (event) => {
if (event.shiftKey && event.key === 'R' && event.target.isContentEditable) {
event.preventDefault();
const id = event.target.getAttribute('editable-id');
beauDrapeau().clear(id);
}
};
if (beauDrapeau.isEditMode) {
document.addEventListener('keydown', handleKeyDown);
} else {
document.removeEventListener('keydown', handleKeyDown);
}
}
}),
style(el, pos){
// Add styles
el.style.position = 'fixed';
el.style.top = '2rem';
el.style.padding = '.75em .5em';
el.style.right = `${5.5 * pos + 1}rem`;
el.style.backgroundColor = '#f3f3f3';
el.style.borderRadius = '3px';
el.style.border = '1px solid currentColor';
el.style.zIndex = '99';
el.style.color = '#0064ff';
el.style.textAlign = 'center';
el.style.fontFamily = 'sans-serif';
el.style.backgroundColor = '#e1e1e1';
el.style.cursor = 'pointer';
el.classList = 'no-print';
return el
}
}),
data: () => ({
/**
* Resets the `breaking` property of the given data object to an empty array.
* @param {object} data - The data object to update.
*/
clearBreaking: (data) => {
data.breaking = [];
},
/**
* Check if element had breaking point element
* @param {Element} editableElement - The editable element to inspect.
*/
checkBreaking: (editableElement) => {
let brs = editableElement.querySelectorAll('br:not(.canon)');
return brs.length > 0 ? brs : null
},
/**
* Check if element had inline style
* @param {Element} editableElement - The editable element to inspect.
*/
checkInlineStyle: (editableElement) => {
const style = editableElement.getAttribute('style');
let ls
if(style){
ls = style.match(/letter-spacing:\s*([^;]+);/);
}
return ls ? ls[1] : null
},
/**
* Updates the `breaking` property of the given data object with the indices of the `br` elements in the given editable element.
* @param {Element} editableElement - The editable element to inspect.
* @param {object} data - The data object to update.
*/
updateBreaking: (editableElement, breaking, data) => {
if(!breaking){return}
breaking.forEach((br) => {
const textContent = editableElement.innerHTML
const brIndex = textContent.indexOf(br.outerHTML);
data.breaking.push(brIndex);
});
},
/**
* Updates the `letterspacing` property of the given data object with the value of the `letter-spacing` style for the given editable element.
* @param {Element} editableElement - The editable element to inspect.
* @param {object} data - The data object to update.
*/
updateInlineStyle: (letterSpacing, data) => {
if(!letterSpacing) { return }
data.letterspacing = letterSpacing;
},
sort: () => {
beauDrapeau.data.sort((a, b) => a.id.localeCompare(b.id));
},
/**
* Retrieves the data object for the given editable ID, or creates a new one if it doesn't exist.
* @param {string} editableId - The ID of the editable element.
* @return {object} The data object for the given editable ID, with `id`, `breaking`, and `letterspacing` properties.
*/
getOrCreate: (editableId) => {
const data_template = { id: editableId, breaking: [], letterspacing: null };
let data = beauDrapeau.data.find((item) => item.id === editableId);
if (!data) {
data = data_template;
beauDrapeau.data.push(data);
}
return data;
}
}),
/**
* Saves the `beauDrapeau.data` object to a file called `_beauDrapeau.js`.
*/
save: () => {
// Convert the `beauDrapeau.data` object to a string
const dataString = JSON.stringify(beauDrapeau.data, null, 2);
// Create a new Blob object with the data string
const dataBlob = new Blob([dataString], { type: 'application/json' });
// Pass to download button
beauDrapeau().ui().download(dataBlob)
},
/**
* Toggles the edit mode on or off, updates the UI, and saves the data if edit mode is turned off.
*/
edit: () => {
beauDrapeau.isEditMode = !beauDrapeau.isEditMode;
// update UI
beauDrapeau().ui().toggle().button()
// Toggle the ContentEditable attribute for all elements
beauDrapeau().ui().toggle().contentEditable()
// Initialize event listeners
beauDrapeau().ui().event().letterSpacing();
beauDrapeau().ui().event().reset();
// If Edit mode is off, perform the cleanup and save the data
if (!beauDrapeau.isEditMode) {
const editableElements = document.querySelectorAll('[contentEditable]');
editableElements.forEach((editableElement) => {
const editableId = editableElement.getAttribute('editable-id')
const letterspacing = beauDrapeau().data().checkInlineStyle(editableElement)
const breaking = beauDrapeau().data().checkBreaking(editableElement)
if(letterspacing || breaking){
let data = beauDrapeau().data().getOrCreate(editableId);
// Reset breaking points
beauDrapeau().data().clearBreaking(data);
// Handle breaking elements
beauDrapeau().data().updateBreaking(editableElement, breaking, data);
// Handle inline styles
beauDrapeau().data().updateInlineStyle(letterspacing, data);
}
});
// Sort
if(beauDrapeau.data.length > 0){
beauDrapeau().data().sort(beauDrapeau.data);
}
// Save
console.log(beauDrapeau.data)
beauDrapeau().save()
}
},
clear: (ids) => {
// Convert the `ids` argument to an array if it's not already an array
if (!Array.isArray(ids)) {
ids = [ids];
}
// Loop through each ID in the `ids` array
ids.forEach((id) => {
// Get the corresponding DOM element by ID
console.log("Clear element ", id)
const element = document.querySelector("[editable-id='"+id+"'");
if (element) {
// Remove all <br> elements with class "canon"
const brElements = element.querySelectorAll('br:not(.canon)');
brElements.forEach((br) => {
br.remove();
});
// Remove inline letter-spacing styles
element.style.letterSpacing = '';
// Find the corresponding data object in `beauDrapeau.data`
const dataIndex = beauDrapeau.data.findIndex((data) => data.id === id);
if (dataIndex !== -1) {
beauDrapeau.data = beauDrapeau.data.filter((data) => data.id !== id);
}
}
});
beauDrapeau().save()
},
})

View file

@ -0,0 +1,15 @@
{
"name": "Beau Drapeau",
"description": "A interface to manually but easily handle line break and letterspacing of textuals elements, in the browser.",
"author": "Benjamin G.",
"licence": "MIT",
"version": "1.0",
"repository": "https://gitlab.com/BenjmnG/beauDrapeau/",
"ui": {
"title": "Beau Drapeau",
"description": "Edit content to force line breaks (click + shift + enter) or change letter spacing (shift + scroll)",
"toggle": true
},
"hook": "beauDrapeau-hook.js",
"script": "beauDrapeau-script.js"
}

View file

@ -0,0 +1,8 @@
{
"name": "Create Index",
"description": "Create index from key words",
"licence": "MIT",
"author": ["Julie Blanc"],
"version": "2.0",
"hook": "createIndex.js"
}

View file

@ -0,0 +1,140 @@
import { Handler } from 'assets/csspageweaver/lib/paged.esm.js';
export default class createIndex extends Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
}
beforeParsed(content) {
newIndex({
content: content,
spanClassIndex: '.book-index',
indexElement: '#book-index',
alphabet: true
});
}
}
function newIndex(config){
const content = config.content;
let indexElements = content.querySelectorAll(config.spanClassIndex);
let arrayIndex = [];
let num = 0;
for(let i = 0; i < indexElements.length; ++i){
let indexElement = indexElements[i];
// create array with all data-book-index
let indexKey = indexElement.dataset.bookIndex;
let indexKeyFirst = indexKey.slice(0, 1);
let newIndexKey;
if(indexKeyFirst == "<"){
if(indexKey.slice(0, 3) == "<i>"){
newIndexKey = indexKey.replace("<i>", "") + "-iTemp";
}else if(indexKey.slice(0, 4) == "<em>"){
newIndexKey = indexKey.replace("<em>", "") + "-emTemp";
}
}else{
newIndexKey = indexKey;
}
arrayIndex.push(newIndexKey);
// create id for span whithout
num++;
if(indexElement.id == ''){ indexElement.id = 'book-index-' + num; }
}
// filter array to remove dublicate and sort by alphabetical order
let newArrayIndex = arrayIndex.filter(onlyUnique).sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
if( a == b) return 0;
return a < b ? -1 : 1;
});
// create <ul> element for the index
let indexElementDiv = content.querySelector(config.indexElement);
if(indexElementDiv != null){
let indexUl = document.createElement("ul");
indexUl.id = "list-index-generated";
indexElementDiv.appendChild(indexUl);
// create <li> element for the index
for(var a = 0; a < newArrayIndex.length; a++){
// create alphabet
if(config.alphabet){
let z = a - 1;
let firstLetter = newArrayIndex[a].toUpperCase().slice(0, 1);
if(a == 0){
let alphabetLiFirst = document.createElement("li");
alphabetLiFirst.classList.add("list-alphabet-element");
alphabetLiFirst.id = "alphabet-element-" + firstLetter;
alphabetLiFirst.innerHTML = firstLetter;
indexUl.appendChild(alphabetLiFirst);
}
if(z >= 0){
let firstLetterPrevious = newArrayIndex[z].toUpperCase().slice(0, 1);
if(firstLetter != firstLetterPrevious){
let alphabetLi = document.createElement("li");
alphabetLi.classList.add("list-alphabet-element");
alphabetLi.id = "alphabet-element-" + firstLetter;
alphabetLi.innerHTML = firstLetter;
indexUl.appendChild(alphabetLi);
}
}
}
// create <li> element for the index
let indexNewLi = document.createElement("li");
indexNewLi.classList.add("list-index-element");
let dataIndex;
if(newArrayIndex[a].substr(newArrayIndex[a].length - 6) == "-iTemp"){
dataIndex = "<i>" + newArrayIndex[a].replace("-iTemp", "");
}else if(newArrayIndex[a].substr(newArrayIndex[a].length - 7) == "-emTemp"){
dataIndex = "<em>" + newArrayIndex[a].replace("-emTemp", "");
}else{
dataIndex = newArrayIndex[a];
}
indexNewLi.dataset.listIndex = dataIndex;
indexUl.appendChild(indexNewLi);
}
let indexLi = content.getElementById('list-index-generated').getElementsByClassName('list-index-element');
for(var n = 0; n < indexLi.length; n++){
// find data and add HTML of the list
let dataIndex = indexLi[n].dataset.listIndex;
let spanIndex = content.querySelectorAll("[data-book-index='" + dataIndex + "']");
indexLi[n].innerHTML = '<span class="index-value">' + dataIndex + '</span><span class="links-pages"></span>';
// add span for link page
spanIndex.forEach(function(elem) {
let spanIndexId = elem.id;
let spanPage = document.createElement("span");
spanPage.classList.add("link-page");
spanPage.innerHTML = '<a href="#' + spanIndexId + '"></a>';
indexLi[n].getElementsByClassName('links-pages')[0].appendChild(spanPage);
});
}
}
}
// function for filter array to remove dublicate
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}

View file

@ -0,0 +1 @@
.DS_Store

View 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.&#8239;<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;
}
```

View 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"
}

View file

@ -0,0 +1,9 @@
@page {
@footnote {
float: bottom;
}
}
.pagedjs_footnote {
float: footnote;
}

View file

@ -0,0 +1,84 @@
/**
* @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 'assets/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;
});
}
}
}

View file

@ -0,0 +1 @@
.DS_Store

View 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 PageMaker 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 PageMaker**
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`.
![](images/full-page-example.png)
## Credits
- [pagedjs.org](https://www.pagedjs.org/)
- [csstree.js](https://github.com/csstree/csstree)
MIT licence, Julie Blanc, 2021

View 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"
}

View 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);
}

View file

@ -0,0 +1,294 @@
/**
* @name Fullpage
* @author Julie Blanc <contact@julie-blanc.fr>
* @see { @link https://gitlab.com/csspageweaver/plugins/fullPage }
*/
import { Handler } from 'assets/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");
}
}
// SPECIFICPAGE ------------------------------------
this.specificPage.forEach(entry => {
const obj = JSON.parse(entry);
const elements = parsed.querySelectorAll(obj.elem);
if (elements.length > 0) {
// pourquoi cest ajouté même si lélément nexiste 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);
}
}
// SPECIFICPAGE ------------------------------------
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");
}
});
}
}

View file

@ -0,0 +1 @@
.DS_Store

View 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.

View 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"
}

View file

@ -0,0 +1,36 @@
/**
* @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);
}
afterPageLayout(pageElement, page, breakToken) {
let pageBox = pageElement.querySelector(".pagedjs_pagebox");
var div = document.createElement('div');
div.classList.add("grid-page");
div.innerHTML = '<div class="grid-column grid-column-0"></div>\
<div class="grid-column grid-column-1"></div>\
<div class="grid-column grid-column-2"></div>\
<div class="grid-column grid-column-3"></div>\
<div class="grid-column grid-column-4"></div>\
<div class="grid-column grid-column-5"></div>\
<div class="grid-column grid-column-6"></div>\
<div class="grid-column grid-column-7"></div>\
<div class="grid-column grid-column-8"></div>\
<div class="grid-column grid-column-9"></div>\
<div class="grid-column grid-column-10"></div>\
<div class="grid-column grid-column-11"></div>\
<div class="grid-column grid-column-12"></div>';
pageBox.appendChild(div);
}
}

View file

@ -0,0 +1,50 @@
/**
* @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 = {}
// set default value
grid.default = {
toggle: 'no-grid'
}
grid.toggle = {}
grid.toggle.input = cssPageWeaver.ui.grid.toggleInput
grid.toggle.label = cssPageWeaver.ui.grid.toggleLabel
/* Previous session */
grid.toggle.value = localStorage.getItem('gridToggle_' + fileTitle) || grid.default.toggle
if(grid.toggle.value == "no-grid"){
body.classList.add('no-grid');
grid.toggle.input.checked = false;
}else if(grid.toggle.value == "grid"){
body.classList.remove('no-grid');
grid.toggle.input.checked = true;
}else{
body.classList.add('no-grid');
localStorage.setItem('gridToggle_' + fileTitle, 'no-grid');
grid.toggle.input.checked = false;
}
/* Grid toggle event */
grid.toggle.input.addEventListener("input", (e) => {
if(e.target.checked){
body.classList.remove('no-grid');
localStorage.setItem('gridToggle_' + fileTitle, 'grid');
}else{
body.classList.add('no-grid');
localStorage.setItem('gridToggle_' + fileTitle, 'no-grid');
}
});
}

View file

@ -0,0 +1,60 @@
@media screen{
.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;
}

View file

@ -0,0 +1 @@
.DS_Store

View 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"
}

View 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;
};

View 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>

View file

@ -0,0 +1 @@
.DS_Store

View 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"
}

View file

@ -0,0 +1,7 @@
:root {
--color-marginBox: purple;
}
.no-marginBoxes{
--color-marginBox: transparent;
}

View 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');
}
});
}

View file

@ -0,0 +1 @@
.DS_Store

View file

@ -0,0 +1,114 @@
# Plugin for sidenotes
This plugin proposes a script for sidenotes in Paged.js.
The plugin creates sidenotes from `span` elements in the text flow. It can be combined with 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.
Note: The plugin moves notes if they overlap or if the last note overflows at the end of the page.
## How to use the plugin
Add this folder to `csspageweaver/plugins/`.
Call the plugin in `csspageweaver/manifest.json`:
```json
"plugins": [
"sidenotes",
// other plugins ...
],
```
## Configuration
In `manifest.json`, you can modify/add some parameters:
```json
"plugins":{
"sidenotes"
},
"pluginsParameters":{
"sidenotes": {
"selector": ".sidenote",
"position": "outside",
"reset": ".chapter",
"align": ".chapter p:first-of-type"
}
},
```
All the parameters are optional.
- `selector` → CSS selector for the note element (must be inline in the HTML), default is `.sidenote`
- `position` → Specifies the position of sidenotes relative to the main text: options are "outside", "inside", "left", "right".
- 'outside': the left pages notes positonned on the margin left and the right pages notes on the margin right. This is the default value.
- 'inside': the left pages notes positonned on the margin right and the right pages notes on the margin left.
- 'left': the notes of both pages are positinoned on the margin left.
- 'right': the notes of both pages are positinoned on the margin left.
- `reset` → CSS selector where you want reset note counter
- `align` → Element to align the first note of the page to, if present on the 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.&#8239;<span class="sidenote">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 sidenotes plugin in the `manifest.json`:
```json
"plugins": [
"inline_notes",
"sidenotes",
// other plugins ...
],
```
## Styling notes
By default, the width of the notes is set on the corresponding margin where the notes are positioned. You can change this width by adjusting the `padding-left` and `padding-right` of your note elements.
To apply specific style depending on the left or right pages, uses the following CSS (where `sidenote` is the class of your notes):
```CSS
.pagedjs_left_page .sidenote {
padding-left: 40px;
padding-right: 20px;
}
.pagedjs_right_page .sidenote {
padding-left: 20px;
padding-right: 40px;
}
```
It's possible to change the styles of call notes and marker notes directly in your stylesheet like in the following code:
```CSS
.pagedjs_sidenote_call{
color: blue;
}
.pagedjs_sidenote_marker{
color: violet;
}
```
## Possible improvement
Currently, there is no way to break a note element across two pages in Paged.js. If all the sidenotes on a page don't fit, the overflowing sidenotes are pushed to the next page. Implementing a feature to split notes across pages would enhance the layout flexibility.

View file

@ -0,0 +1,8 @@
{
"name": "Margin Notes",
"description": "Create margin notes with call & markers",
"author": ["Julie Blanc et Sarah Garcin"],
"licence": "MIT",
"version": "1.0",
"hook": "marginNotes.js"
}

View file

@ -0,0 +1,260 @@
import { Handler } from '/csspageweaver/lib/paged.esm.js';
export default class marginNotes extends Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
this.parameters = cssPageWeaver.features.marginNotes.parameters;
this.notesClass = this.parameters?.selector || ".sidenote";
this.position = this.parameters?.position || "outside";
this.reset = this.parameters?.reset;
this.marker = this.parameters?.marker || ". ";
}
beforeParsed(content) {
let classNotes = this.notesClass;
let marker = this.marker;
if (typeof this.reset === "string" && this.reset.trim() !== "") {
let sections = content.querySelectorAll(this.reset);
sections.forEach(function (section) {
addCallAndMarker(section, classNotes, marker);
});
} else {
addCallAndMarker(content, classNotes, marker);
}
/* NOTE FLOAT ---------------------------------------------------------------------------------- */
let positionRight = 'left: calc(var(--pagedjs-pagebox-width) - var(--pagedjs-margin-left) - var(--pagedjs-margin-right) - 1px); width: var(--pagedjs-margin-right);';
let positionLeft = 'left: calc(var(--pagedjs-margin-left)*-1 - 1px); width: var(--pagedjs-margin-left);'
let notePosition;
switch (this.position) {
case 'inside':
notePosition = '.pagedjs_left_page ' + classNotes + '{' + positionRight + '} \
.pagedjs_right_page ' + classNotes + '{' + positionLeft + '}';
break;
case 'left':
notePosition = '.pagedjs_left_page ' + classNotes + '{' + positionLeft + '} \
.pagedjs_right_page ' + classNotes + '{' + positionLeft + '}';
break;
case 'right':
notePosition = '.pagedjs_left_page ' + classNotes + '{' + positionRight + '} \
.pagedjs_right_page ' + classNotes + '{' + positionRight + '}';
break;
default:
notePosition = '.pagedjs_left_page ' + classNotes + '{' + positionLeft + '} \
.pagedjs_right_page ' + classNotes + '{' + positionRight + '}';
}
/* SPECIFIC CSS ---------------------------------------------------------------------------------- */
addcss('\
' + classNotes + '{\
position: absolute;\
text-align-last: initial;\
box-sizing: border-box;\
}' + notePosition);
} /* end beforeParsed*/
afterPageLayout(pageElement, page, breakToken) {
let classNotes = this.notesClass;
let marker = this.marker;
let notes = pageElement.querySelectorAll(classNotes);
let noteOverflow = false;
let notesHeightAll = [];
if (typeof (notes) != 'undefined' && notes != null && notes.length != 0) {
for (let n = 0; n < notes.length; ++n) {
// Display notes of the page
notes[n].style.display = "inline-block";
// Add height of the notes to array notesHeightAll
let noteHeight = notes[n].offsetHeight;
notesHeightAll.push(noteHeight);
// Add margins of the notes to array notesHeightAll
if (n >= 1) {
let margins = biggestMargin(notes[n - 1], notes[n]);
notesHeightAll.push(margins);
}
}
/* FIT PAGE ------------------------------------------------------------------------------------- */
// Calculate if all notes fit on the page;
let reducer = (accumulator, currentValue) => accumulator + currentValue;
let allHeight = notesHeightAll.reduce(reducer);
let maxHeight = pageElement.querySelectorAll(".pagedjs_page_content")[0].offsetHeight;
if (allHeight > maxHeight) {
/* IF DOESN'T FIT ----------------------------------------------------------------------------- */
// positions all the notes one after the other starting from the top
notes[0].style.top = parseInt(window.getComputedStyle(notes[0]).marginBottom, 10) * -1 + "px";
for (let a = 1; a < notes.length; ++a) {
let notePrev = notes[a - 1];
let newMargin = biggestMargin(notePrev, notes[a]);
let newTop = notePrev.offsetTop + notePrev.offsetHeight - marginNoteTop(notes[a]) + newMargin;
notes[a].style.top = newTop + "px";
}
// alert
let pageNumber = pageElement.dataset.pageNumber;
alert("Rendering issue \n ☞ A marginal note overflow on page " + pageNumber + " (this is because there is too many on this page and paged.js can't breaks notes between pages for now.)");
noteOverflow = true;
} else {
/* PUSH DOWN ---------------------------------------------------- */
for (let i = 0; i < notes.length; ++i) {
if (i >= 1) {
let noteTop = notes[i].offsetTop;
let notePrev = notes[i - 1];
let newMargin = biggestMargin(notes[i], notePrev);
let notePrevBottom = notePrev.offsetTop - marginNoteTop(notePrev) + notePrev.offsetHeight + newMargin;
// Push down the note to bottom if it's over the previous one
if (notePrevBottom > noteTop) {
notes[i].style.top = notePrevBottom + "px";
}
}
}
/* PUSH UP ---------------------------------------------- */
// Height of the page content
let contentHeight = pageElement.querySelectorAll(".pagedjs_page_content")[0].querySelectorAll("div")[0].offsetHeight;
// Check if last note overflow
let nbrLength = notes.length - 1;
let lastNote = notes[nbrLength];
let lastNoteHeight = lastNote.offsetHeight + marginNoteTop(lastNote);
let noteBottom = lastNote.offsetTop + lastNoteHeight;
if (noteBottom > contentHeight) {
// Push up the last note
lastNote.style.top = contentHeight - lastNoteHeight + "px";
// Push up previous note(s) if if it's over the note
for (let i = nbrLength; i >= 1; --i) {
let noteLastTop = notes[i].offsetTop;
let notePrev = notes[i - 1];
let notePrevHeight = notePrev.offsetHeight;
let newMargin = biggestMargin(notePrev, notes[i]);
let notePrevBottom = notePrev.offsetTop + notePrev.offsetHeight + newMargin;
if (notePrevBottom > noteLastTop) {
notePrev.style.top = notes[i].offsetTop - marginNoteTop(notePrev) - notePrevHeight - newMargin + "px";
}
}
} /* end push up */
}
}
}/* end afterPageLayout*/
}
/* FUNCTIONS --------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------- */
//
function addCallAndMarker(section, classNotes, marker){
let notes = section.querySelectorAll(classNotes);
for (let i = 0; i < notes.length; ++i) {
let num = i + 1;
// Add call notes
var spanCall = document.createElement("span");
spanCall.classList.add("note-call");
spanCall.classList.add("note-call_" + classNotes);
spanCall.dataset.noteCall = classNotes + '-' + num;
spanCall.innerHTML = num;
notes[i].parentNode.insertBefore(spanCall, notes[i]);
// Add marker notes
var spanMarker = document.createElement("span");
spanMarker.classList.add("note-marker");
spanMarker.classList.add("note-marker_" + classNotes);
spanMarker.dataset.noteMarker = classNotes + '-' + num;
spanMarker.innerHTML = num + marker;
notes[i].prepend(spanMarker);
// Hide notes to avoid rendering problems
notes[i].style.display = "none";
}
}
// MARGINS
function marginNoteTop(elem) {
let marginTop = parseInt(window.getComputedStyle(elem).marginTop, 10)
return marginTop;
}
function marginNoteBottom(elem) {
let marginBottom = parseInt(window.getComputedStyle(elem).marginBottom, 10)
return marginBottom;
}
function biggestMargin(a, b) {
let margin;
let marginBottom = marginNoteBottom(a);
let marginTop = marginNoteTop(b);
if (marginBottom > marginTop) {
margin = marginBottom;
} else {
margin = marginTop;
}
return margin;
}
// ADD CSS
function addcss(css) {
var head = document.getElementsByTagName('head')[0];
var s = document.createElement('style');
s.setAttribute('type', 'text/css');
if (s.styleSheet) { // IE
s.styleSheet.cssText = css;
} else {// the world
s.appendChild(document.createTextNode(css));
}
head.appendChild(s);
}
// CAMEL CLASS NOTE
function toCamelClassNote(elem) {
let splitClass = elem.split("-");
if (splitClass.length > 1) {
for (let s = 1; s < splitClass.length; ++s) {
let strCapilize = splitClass[s].charAt(0).toUpperCase() + splitClass[s].slice(1)
splitClass[s] = strCapilize;
}
}
let reducer = (accumulator, currentValue) => accumulator + currentValue;
let classCamel = splitClass.reduce(reducer);
return classCamel;
}

View file

@ -0,0 +1 @@
.DS_Store

View 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"
}

View 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)
}
})
}

View 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;
}

View 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>

View file

@ -0,0 +1 @@
.DS_Store

View file

@ -0,0 +1,7 @@
{
"name": "Regex Typo",
"description": "Script for handling orthotypography.",
"author": "Julie Blanc et Sarah Garcin",
"repository": "https://gitlab.com/JulieBlanc/typesetting-tools",
"hook": "regexTypo-hook.js"
}

View file

@ -0,0 +1,24 @@
/**
* @name Regex Typo
* @file Script for handling orthotypography.
* @author Julie Blanc <contact@julie-blanc.fr> modified by Sarah Garcin
* @see { @link https://gitlab.com/JulieBlanc/typesetting-tools }
* @see { @link https://gitlab.com/csspageweaver/plugins/regexTypo/ }
*/
import { Handler } from '/csspageweaver/lib/paged.esm.js';
import { orthotypo, exposants, noHyphens } from '/csspageweaver/plugins/regexTypo/regexTypo.js';
export default class regextypoHook extends Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller)
}
beforeParsed(content) {
orthotypo(content);
exposants(content);
noHyphens(content);
}
}

View file

@ -0,0 +1,312 @@
export function orthotypo(content){
var nodes = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, null, null);
let array = [
{
// XIème = XIe
reg: /(X|I|V)ème/g,
repl: '$1e'
},
{
// french open quotes
reg: /\"([A-Za-zÀ-ÖØ-öø-ÿœŒ])/g,
repl: '«$1'
},
{
// french close quotes
reg: /([A-Za-zÀ-ÖØ-öø-ÿœŒ])\"/g,
repl: '$1»'
},
{
// real apostrophe
reg: /\'/g,
repl: ''
},
{
// real suspension points
reg: /\.+\.+\./g,
repl: '\u2026'
},
{
// delete all spaces before punctuation !?;:»›”)].,
reg: /\s+([!?;:»›”)\]\.\,])/g,
repl: '$1'
},
{
// add narrow no break space before !?;:»›
reg: /([!?;:»›])/g,
repl: '\u202F$1'
},
{
// delete all spaces after «‹“[(
reg: /([«‹“\[(])\s+/g,
repl: '$1'
},
{
// add narrow no break space after «‹
reg: /([«‹])/g,
repl: '$1\u202F'
},
{
// OPTION 1 : no break space after two letter words (if not follow by an other two letter word)
// reg: /\s+([a-zØ-öø-ÿœ]{2})\s+([A-Za-zÀ-ÖØ-öø-ÿœŒ]{3,})/gi,
// repl: ' $1\u00A0$2'
// OPTION 2: no break space after some two letter words
reg: /\s(le|la|un|une|ce|ces|il|on|les|des|du|ils)\s+/g,
repl: ' $1\u00A0'
},
{
// if prev OPTION 2: no break space after successive two letter words
reg: /\s+([a-zØ-öø-ÿœ]{2})\s+([A-Za-zÀ-ÖØ-öø-ÿœŒ]{2})\s+/g,
repl: ' $1 $2\u00A0'
},
{
// no break space after one letter words
reg: /\s+([a-zà])\s+/gi,
repl: ' $1\u00A0'
},
{
// no break space after first word (2-5 letter) of the sentence
reg: /\.\s([A-ZÀ-Ö])([A-Za-zÀ-ÖØ-öø-ÿœŒ]{1,5})\s+/g,
repl: '. $1$2\u00A0'
},
{
// no break space into names
reg: /([A-ZÀ-ÖØŒ])([A-Za-zÀ-ÖØ-öø-ÿœŒ]+)\s+([A-ZÀ-ÖØŒ])([A-Za-zÀ-ÖØ-öø-ÿœŒ]+)/g,
repl: '$1$2\u00A0$3$4'
},
{
// no break space before Caps + .
reg: /\s([A-ZÀ-ÖØŒ])\./g,
repl: '\u00A0$1. '
},
{
// no break space before 'siècles'
reg: /(X|I|V)(er|e)\s+siècle/g,
repl: '$1$2\u00A0siècles'
},
]
var node;
while (node = nodes.nextNode()) {
// search if text is into a <code> element
if(!node.parentElement){
return
}
var code = node.parentElement.closest("code");
// if not, apply replacements
if(code == null){
for (var i = 0; i < array.length; i++) {
node.textContent = node.textContent.replace(array[i].reg, array[i].repl);
}
}
}
}
function spaces( content ){
let all = content.querySelectorAll('p, span');
all.forEach(element => {
element.innerHTML = spacesRegex(element.innerHTML)
})
}
function spacesRegex(elem){
let array = [
{
// french open quotes
reg: /\"([A-Za-zÀ-ÖØ-öø-ÿœŒ])/g,
repl: '«$1'
},
{
// french close quotes
reg: /([A-Za-zÀ-ÖØ-öø-ÿœŒ])\"/g,
repl: '$1»'
},
{
// real apostrophe
reg: /\'/g,
repl: ''
},
{
// real suspension points
reg: /\.+\.+\./g,
repl: '\u2026'
},
{
// delete all spaces before punctuation !?;:»›”)].,
reg: /\s+([!?;:»›”)\]\.\,])/g,
repl: '$1'
},
{
// add narrow no break space before !?;:»›
reg: /([!?;:»›])/g,
repl: '\u202F$1'
},
{
// delete all spaces after «‹“[(
reg: /([«‹“\[(])\s+/g,
repl: '$1'
},
{
// add narrow no break space after «‹
reg: /([«‹])/g,
repl: '$1\u202F'
},
{
// OPTION 1 : no break space after two letter words (if not follow by an other two letter word)
// reg: /\s+([a-zØ-öø-ÿœ]{2})\s+([A-Za-zÀ-ÖØ-öø-ÿœŒ]{3,})/gi,
// repl: ' $1\u00A0$2'
// OPTION 2: no break space after some two letter words
reg: /\s(le|la|un|une|ce|ces|il|on|les|des|du|ils)\s+/g,
repl: ' $1\u00A0'
},
{
// if prev OPTION 2: no break space after successive two letter words
reg: /\s+([a-zØ-öø-ÿœ]{2})\s+([A-Za-zÀ-ÖØ-öø-ÿœŒ]{2})\s+/g,
repl: ' $1 $2\u00A0'
},
{
// no break space after one letter words
reg: /\s+([a-zà])\s+/gi,
repl: ' $1\u00A0'
},
{
// no break space after first word (2-5 letter) of the sentence
reg: /\.\s([A-ZÀ-Ö])([A-Za-zÀ-ÖØ-öø-ÿœŒ]{1,5})\s+/g,
repl: '. $1$2\u00A0'
},
{
// no break space into names
reg: /([A-ZÀ-ÖØŒ])([A-Za-zÀ-ÖØ-öø-ÿœŒ]+)\s+([A-ZÀ-ÖØŒ])([A-Za-zÀ-ÖØ-öø-ÿœŒ]+)/g,
repl: '$1$2\u00A0$3$4'
},
{
// no break space before Caps + .
reg: /\s([A-ZÀ-ÖØŒ])\./g,
repl: '\u00A0$1. '
},
{
// no break space before 'siècles'
reg: /(X|I|V)(er|e)\s+siècle/g,
repl: '$1$2\u00A0siècles'
},
// {
// // 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'
// },
// {
// // p. and pp. in blibliography
// reg: /(\spp?\.)\s?(\d)/g,
// repl: '$1\u00A0$2'
// }
]
for (var i = 0; i < array.length; i++) {
elem = elem.replace(array[i].reg, array[i].repl);
console.log(elem);
}
return elem;
}
export function noHyphens( content ){
let all = content.querySelectorAll('p');
// all french caracteres: [A-Za-zÀ-ÖØ-öø-ÿœŒ]
all.forEach(element => {
element.innerHTML = noHyphensRegex(element.innerHTML)
})
}
function noHyphensRegex(elem){
let array = [
{
// no break space into names
reg: /([A-ZÀ-ÖØŒ])([A-Za-zÀ-ÖØ-öø-ÿœŒ]+)\s+([A-ZÀ-ÖØŒ])([A-Za-zÀ-ÖØ-öø-ÿœŒ]+)/g,
repl: '$1$2\u00A0$3$4'
},
{
// no break space before Caps + .
reg: /\s([A-ZÀ-ÖØŒ])\./g,
repl: '\u00A0$1. '
},
{
// no break space before 'siècles'
reg: /(X|I|V)(er|e)\s+siècle/g,
repl: '$1$2\u00A0siècles'
},
{
// règles le problème de 1ep qui met le e en exposant
reg: '1<sup>e</sup>p',
repl: '1ep'
}
]
for (var i = 0; i < array.length; i++) {
elem = elem.replace(array[i].reg, array[i].repl);
}
return elem;
}
export function exposants(content){
let all = content.querySelectorAll('p, span');
all.forEach(element => {
element.innerHTML = exposantsRegex(element.innerHTML)
})
}
function exposantsRegex(elem){
let array = [
{
// numéros
reg: /\sno\.?\s?(\d+)/g,
repl: ' n<sup>o</sup>&nbsp;$1'
},
{
// siècles + small caps
reg: /(XXI|XX|XIX|XVIII|XVII|XVI|XV|xxi|xx|xix|xviii|xvii|xvi|xv)(e|er)/g,
repl: '<span style="text-transform: lowercase; font-variant: small-caps;">$1</span><sup>$2</sup>'
},
{
// exposant e après chiffres
reg: /(\d+)(er|e)[\s\\u00A0]/g,
repl: '$1<sup>$2</sup>'
},
{
// exposant e après chiffres
reg: '22e',
repl: '22<sup>e</sup>'
},
{
// exposant e après chiffres
reg: '4e éd.',
repl: '4<sup>e</sup> éd.'
},
{
// exposant e après chiffres
reg: 'IVe',
repl: 'IV<sup>e</sup>'
},
]
for (var i = 0; i < array.length; i++) {
elem = elem.replace(array[i].reg, array[i].repl);
}
return elem;
}

View file

@ -0,0 +1 @@
.DS_Store

View file

@ -0,0 +1,17 @@
# Reload In Place
## Configuration
In `manifest.json`
```json
"plugins":{
"inlineNotes"
},
"pluginsParameters":{
"reloadInPlace": {
"blur": false,
"behavior": "instant"
}
},
```

View file

@ -0,0 +1,9 @@
{
"name": "Reload In Place",
"description": "A simple script to add your CSS Page Weaver project. On reload, it will make the web browser scroll to the place it was before reload. ",
"author": ["Nicolas Taffin", "Sameh Chafik"],
"licence": "MIT",
"version": 2,
"repository": "https://gitlab.com/nicolastaf/pagedjs-reload-in-place",
"hook": "reloadInPlace.js"
}

View file

@ -0,0 +1,174 @@
/**
* @name Reload-in-place v2.0
* @desc A simple script to add to your pagedjs project. On reload, it will make the web browser scroll to the place it was before reload.
* Useful when styling or proof correcting your book. Multi docs compatible and doesn't wait for complete compilation to go.
* @author Nicolas Taffin
* @author Sameh Chafik
* @author (adapted by) Benjamin G. <ecrire@bnjm.eu>
* @license MIT
* @see { @link https://gitlab.com/csspageweaver/plugins/reloadInPlace }
*/
import { Handler } from '/csspageweaver/lib/paged.esm.js';
export default class reloadInPlace extends Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
this.parameters = cssPageWeaver.features.reloadInPlace.parameters || {}
this.isScrollBlured = this.parameters.blur || true;
this.scrollBehavior = this.parameters.scrollBehavior || 'instant';
// set a "unique" filename based on title element, in case several books are opened
this.fileTitle = document.getElementsByTagName("title")[0].text.replace(/ /g, "");
}
beforeParsed() {
// separate human / machine scroll
this.parameters.machineScroll = false;
// check pagedJS ended compilation
this.parameters.cssPageWeaverEnd = false;
// Make it blur if needed
this.isBlur()
}
afterParsed() {
this.moveFast();
}
afterRendered(pages) {
this.parameters.cssPageWeaverEnd = true;
// slow down a bit save position pace
var slowSave = this.debounce(() => {
if(!this.parameters.machineScroll) {
this.saveAmountScrolled();
}
}, 100); // save frequency
setTimeout(function(){
window.addEventListener('scroll', slowSave);
}, 1000); // wait a bit before starting position save
}
getDocHeight() {
var D = document;
return Math.max(
D.body.scrollHeight, D.documentElement.scrollHeight,
D.body.offsetHeight, D.documentElement.offsetHeight,
D.body.clientHeight, D.documentElement.clientHeight
)
}
saveAmountScrolled(){
var scrollArray = [];
var scrollTop = window.pageYOffset || (document.documentElement || document.body.parentNode || document.body).scrollTop
if (!this.parameters.machineScroll) {
var scrollLeft = window.pageXOffset || (document.documentElement || document.body.parentNode || document.body).scrollLeft
scrollArray.push({ X: Math.round(scrollLeft), Y: Math.round(scrollTop) });
//console.log("Saved ", scrollArray);
localStorage['reloadInPlace-' + this.fileTitle] = JSON.stringify(scrollArray);
}
}
isBlur(){
// Apply a blur effect if scroll blurring is enabled
if (this.isScrollBlured) {
var styleEl = document.createElement('style');
styleEl.setAttribute("data-reload-in-place", true)
document.head.appendChild(styleEl);
this.styleSheet = styleEl.sheet;
this.styleSheet.insertRule('.pagedjs_pages { filter: blur(3px); }', 0);
}
}
retrievePosition(){
// Retrieve saved scroll data from localStorage
var savedData = localStorage.getItem('reloadInPlace-' + this.fileTitle);
if (savedData) {
// Parse the saved data to get the scroll positions
var scrollArray = JSON.parse(savedData);
this.scrollTop = scrollArray[0].Y;
this.scrollLeft = scrollArray[0].X;
} else {
// Default scroll positions if no saved data is found
this.scrollTop = 0;
this.scrollLeft = 0;
}
}
/**
* Adjusts the scroll position of the window based on saved data.
* This function handles scrolling behavior when the document height changes,
* ensuring the view returns to a previously saved scroll position or the bottom of the document.
*
* @param {Object} _ - ReloadInPlace scope
*/
moveFast() {
// Set the machine scroll flag to true
this.parameters.machineScroll = true;
this.retrievePosition()
// Get the window height
var winheight = window.innerHeight || (document.documentElement || document.body).clientHeight;
let _ = this
// Set up an interval to adjust the scroll position
_.currentInterval = setInterval(() => {
// Get the current document height
_.docheight = _.getDocHeight();
// Check if the saved scroll position is beyond the current document height
if ( _.scrollTop > 0 && _.scrollTop > _.docheight - winheight && !_.parameters.cssPageWeaverEnd) {
// Scroll to the bottom of the document
window.scrollTo(_.scrollLeft, _.docheight, _.scrollBehavior);
} else {
// Scroll to the saved position
window.scrollTo(_.scrollLeft, _.scrollTop, _.scrollBehavior);
// Clear interval
clearInterval(_.currentInterval);
// set a timeout to finalize the scroll position
setTimeout(function () {
window.scrollTo(_.scrollLeft, _.scrollTop, _.scrollBehavior);
// Reset the machine scroll flag
_.parameters.machineScroll = false;
// Remove the blur effect if it was applied
if (_.isScrollBlured) {
_.styleSheet.deleteRule(0);
}
}, 50); // Delay to start
}
}, 50); // Refresh frequency
}
debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
}

View file

@ -0,0 +1 @@
.DS_Store

Some files were not shown because too many files have changed in this diff Show more