Compare commits

..

No commits in common. "9af36fb4221c295e04ea381ba5a36b7be93e2aa1" and "960f509d5c466195ef87ed69030696eeb0400a38" have entirely different histories.

19 changed files with 209 additions and 851 deletions

7
package-lock.json generated
View file

@ -8,7 +8,6 @@
"name": "geoproject", "name": "geoproject",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@melloware/coloris": "^0.25.0",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"pagedjs": "^0.4.3", "pagedjs": "^0.4.3",
"pinia": "^3.0.4", "pinia": "^3.0.4",
@ -533,12 +532,6 @@
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@melloware/coloris": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@melloware/coloris/-/coloris-0.25.0.tgz",
"integrity": "sha512-RBWVFLjWbup7GRkOXb9g3+ZtR9AevFtJinrRz2cYPLjZ3TCkNRGMWuNbmQWbZ5cF3VU7aQDZwUsYgIY/bGrh2g==",
"license": "MIT"
},
"node_modules/@rolldown/pluginutils": { "node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.50", "version": "1.0.0-beta.50",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz",

View file

@ -9,7 +9,6 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@melloware/coloris": "^0.25.0",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"pagedjs": "^0.4.3", "pagedjs": "^0.4.3",
"pinia": "^3.0.4", "pinia": "^3.0.4",

View file

@ -0,0 +1 @@

View file

@ -1,21 +0,0 @@
button {
cursor: pointer;
border: 1px solid var(--color-browngray-300);
color: var(--color-browngray-300);
border-radius: var(--border-radius);
padding: 0.1rem 0.3rem;
&.active {
border: 1px solid #000;
color: #000;
}
&.tab {
&.active {
background-color: #000;
color: #fff;
border: none;
}
}
}

View file

@ -1,72 +0,0 @@
select,
input[type="text"],
input[type="number"] {
background-color: var(--color-browngray-300);
}
.field--view-only {
opacity: 0.3;
}
.settings-section {
h2 {
border-bottom: 1px solid #000;
}
.settings-subsection:not(:last-child) {
border-bottom: 1px solid var(--color-browngray-050);
}
.settings-subsection {
padding: 0.5rem 0;
.field {
display: flex;
label {
width: 50%;
}
.input-with-unit {
display: flex;
}
.input-with-color {
.clr-field {
display: flex;
button {
position: absolute;
transform: none;
height: 1rem;
top: auto;
right: auto;
cursor: pointer;
}
input {
padding-left: 2.5rem;
}
}
}
}
&.margins {
display: flex;
flex-wrap: wrap;
h3 {
width: 100%;
}
.field {
width: 50%;
label {
width: 50%;
}
.input-with-unit {
input {
width: 50%;
}
}
}
}
}
}

View file

@ -1,27 +0,0 @@
body,
html {
padding: 0;
margin: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
}
input,
select {
border: none;
outline: none;
border-radius: var(--border-radius);
}
button {
background-color: transparent;
border: none;
}

View file

@ -1,15 +0,0 @@
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
a,
input,
select,
figcaption,
label {
font-family: sans-serif;
}

View file

@ -1,8 +0,0 @@
:root {
--color-panel-bg: #e8e6e5;
--color-browngray-050: #f5f3f0;
--color-browngray-200: #d0c4ba;
--color-browngray-300: #b5a9a1;
--border-radius: 0.2rem;
}

View file

@ -1,145 +0,0 @@
body,
html {
padding: 0;
margin: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
}
input,
select {
border: none;
outline: none;
border-radius: var(--border-radius);
}
button {
background-color: transparent;
border: none;
}
:root {
--color-panel-bg: #e8e6e5;
--color-browngray-050: #f5f3f0;
--color-browngray-200: #d0c4ba;
--color-browngray-300: #b5a9a1;
--border-radius: 0.2rem;
}
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
a,
input,
select,
figcaption,
label {
font-family: sans-serif;
}
/* PagedJS print styles */
@page {
size: A4;
margin: 20mm 15mm 26mm 15mm;
}
h2 {
-moz-column-break-before: page;
break-before: page;
}
@page {
@bottom-center {
content: string(title);
}
}
.chapter > h2 {
string-set: title content(text);
}
select,
input[type=text],
input[type=number] {
background-color: var(--color-browngray-300);
}
.field--view-only {
opacity: 0.3;
}
.settings-section h2 {
border-bottom: 1px solid #000;
}
.settings-section .settings-subsection:not(:last-child) {
border-bottom: 1px solid var(--color-browngray-050);
}
.settings-section .settings-subsection {
padding: 0.5rem 0;
}
.settings-section .settings-subsection .field {
display: flex;
}
.settings-section .settings-subsection .field label {
width: 50%;
}
.settings-section .settings-subsection .field .input-with-unit {
display: flex;
}
.settings-section .settings-subsection .field .input-with-color .clr-field {
display: flex;
}
.settings-section .settings-subsection .field .input-with-color .clr-field button {
position: absolute;
transform: none;
height: 1rem;
top: auto;
right: auto;
cursor: pointer;
}
.settings-section .settings-subsection .field .input-with-color .clr-field input {
padding-left: 2.5rem;
}
.settings-section .settings-subsection.margins {
display: flex;
flex-wrap: wrap;
}
.settings-section .settings-subsection.margins h3 {
width: 100%;
}
.settings-section .settings-subsection.margins .field {
width: 50%;
}
.settings-section .settings-subsection.margins .field label {
width: 50%;
}
.settings-section .settings-subsection.margins .field .input-with-unit input {
width: 50%;
}
button {
cursor: pointer;
border: 1px solid var(--color-browngray-300);
color: var(--color-browngray-300);
border-radius: var(--border-radius);
padding: 0.1rem 0.3rem;
}
button.active {
border: 1px solid #000;
color: #000;
}
button.tab.active {
background-color: #000;
color: #fff;
border: none;
}/*# sourceMappingURL=style.css.map */

View file

@ -1 +0,0 @@
{"version":3,"sources":["src/_reset.scss","style.css","src/_variables.scss","src/_text.scss","src/_print-styles.scss","src/_forms.scss","src/_buttons.scss"],"names":[],"mappings":"AAAA;;EAEE,UAAA;EACA,SAAA;ACCF;;ADEA;;;;;;EAME,SAAA;ACCF;;ADEA;;EAEE,YAAA;EACA,aAAA;EAEA,mCAAA;ACAF;;ADGA;EACE,6BAAA;EACA,YAAA;ACAF;;ACzBA;EACE,yBAAA;EACA,8BAAA;EACA,8BAAA;EACA,8BAAA;EAEA,uBAAA;AD2BF;;AEjCA;;;;;;;;;;;;;EAaE,uBAAA;AFoCF;;AGjDA,yBAAA;AACA;EACE,QAAA;EACA,2BAAA;AHoDF;AGlDA;EACE,8BAAA;OAAA,kBAAA;AHoDF;;AGjDA;EACE;IACE,sBAAA;EHoDF;AACF;AGlDA;EACE,+BAAA;AHoDF;;AInEA;;;EAGE,4CAAA;AJsEF;;AInEA;EACE,YAAA;AJsEF;;AIlEE;EACE,6BAAA;AJqEJ;AInEE;EACE,mDAAA;AJqEJ;AIlEE;EACE,iBAAA;AJoEJ;AIlEI;EACE,aAAA;AJoEN;AInEM;EACE,UAAA;AJqER;AIlEM;EACE,aAAA;AJoER;AIhEQ;EACE,aAAA;AJkEV;AIjEU;EACE,kBAAA;EACA,eAAA;EACA,YAAA;EACA,SAAA;EACA,WAAA;EACA,eAAA;AJmEZ;AIhEU;EACE,oBAAA;AJkEZ;AI5DI;EACE,aAAA;EACA,eAAA;AJ8DN;AI7DM;EACE,WAAA;AJ+DR;AI7DM;EACE,UAAA;AJ+DR;AI7DQ;EACE,UAAA;AJ+DV;AI3DU;EACE,UAAA;AJ6DZ;;AK9HA;EACE,eAAA;EAEA,4CAAA;EACA,iCAAA;EACA,mCAAA;EACA,sBAAA;ALgIF;AK9HE;EACE,sBAAA;EACA,WAAA;ALgIJ;AK5HI;EACE,sBAAA;EACA,WAAA;EACA,YAAA;AL8HN","file":"style.css"}

View file

@ -1,6 +0,0 @@
@import "src/_reset.scss";
@import "src/_variables.scss";
@import "src/_text.scss";
@import "src/_print-styles.scss";
@import "src/_forms.scss";
@import "src/_buttons.scss";

View file

@ -8,8 +8,8 @@
<?= e($page->isHomePage() != true, $page->title() . ' - ') . $site->title() ?> <?= e($page->isHomePage() != true, $page->title() . ' - ') . $site->title() ?>
</title> </title>
<link rel="stylesheet" href="<?= url('assets/css/style..css') ?>"> <link rel="stylesheet" href="<?= url('assets/pagedjs-interface.css') ?>">
<link rel="stylesheet" href="<?= url('assets/css/pagedjs-interface.css') ?>"> <link rel="stylesheet" href="<?= url('assets/stylesheet.css') ?>">
<!-- À SUPPRIMER EN PRODUCTION --> <!-- À SUPPRIMER EN PRODUCTION -->
<meta name="robots" content="noindex, nofollow, noarchive"> <meta name="robots" content="noindex, nofollow, noarchive">

View file

@ -1,6 +1,7 @@
<script setup> <script setup>
import PagedJsWrapper from './components/PagedJsWrapper.vue'; import PagedJsWrapper from './components/PagedJsWrapper.vue';
import EditorPanel from './components/editor/EditorPanel.vue'; import EditorPanel from './components/editor/EditorPanel.vue';
import StylesheetViewer from './components/StylesheetViewer.vue';
import ElementPopup from './components/ElementPopup.vue'; import ElementPopup from './components/ElementPopup.vue';
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import { useStylesheetStore } from './stores/stylesheet'; import { useStylesheetStore } from './stores/stylesheet';
@ -41,10 +42,7 @@ const renderPreview = async (shouldReloadFromFile = false) => {
`; `;
iframe.onload = () => { iframe.onload = () => {
iframe.contentDocument.addEventListener( iframe.contentDocument.addEventListener('click', elementPopup.value.handleIframeClick);
'click',
elementPopup.value.handleIframeClick
);
setTimeout(() => { setTimeout(() => {
const scrollHeight = iframe.contentDocument.documentElement.scrollHeight; const scrollHeight = iframe.contentDocument.documentElement.scrollHeight;
@ -57,12 +55,9 @@ const renderPreview = async (shouldReloadFromFile = false) => {
}; };
}; };
watch( watch(() => stylesheetStore.content, () => {
() => stylesheetStore.content,
() => {
renderPreview(); renderPreview();
} });
);
onMounted(() => renderPreview(true)); onMounted(() => renderPreview(true));
</script> </script>
@ -76,6 +71,8 @@ onMounted(() => renderPreview(true));
<iframe ref="previewFrame" id="preview-frame"></iframe> <iframe ref="previewFrame" id="preview-frame"></iframe>
<StylesheetViewer :stylesheet="stylesheetStore.content" />
<ElementPopup ref="elementPopup" :iframeRef="previewFrame" /> <ElementPopup ref="elementPopup" :iframeRef="previewFrame" />
</template> </template>
@ -83,8 +80,8 @@ onMounted(() => renderPreview(true));
#preview-frame { #preview-frame {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 250px;
width: 100vw; width: calc(100% - 600px);
height: 100vh; height: 100vh;
border: none; border: none;
} }

View file

@ -1,5 +1,5 @@
<template> <template>
<div id="stylesheet-viewer"> <aside id="stylesheet-viewer">
<div class="header"> <div class="header">
<h3>Stylesheet</h3> <h3>Stylesheet</h3>
<label class="toggle"> <label class="toggle">
@ -17,7 +17,7 @@
@input="handleInput" @input="handleInput"
spellcheck="false" spellcheck="false"
></textarea> ></textarea>
</div> </aside>
</template> </template>
<script setup> <script setup>
@ -53,9 +53,14 @@ const handleInput = (event) => {
<style scoped> <style scoped>
#stylesheet-viewer { #stylesheet-viewer {
display: flex; position: fixed;
flex-direction: column; top: 0;
height: 100%; right: 0;
width: 350px;
height: 100vh;
padding: 1rem;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
overflow-y: auto;
background: #282c34; background: #282c34;
color: #fff; color: #fff;
} }
@ -125,7 +130,7 @@ h3 {
.readonly { .readonly {
margin: 0; margin: 0;
flex: 1; height: calc(100vh - 5rem);
overflow-y: auto; overflow-y: auto;
padding: 0.5rem; padding: 0.5rem;
background: #1e1e1e; background: #1e1e1e;
@ -140,7 +145,7 @@ h3 {
textarea { textarea {
width: 100%; width: 100%;
flex: 1; height: calc(100vh - 5rem);
background: #1e1e1e; background: #1e1e1e;
color: #abb2bf; color: #abb2bf;
border: none; border: none;

View file

@ -3,7 +3,6 @@
<nav class="tabs"> <nav class="tabs">
<button <button
type="button" type="button"
class="tab"
:class="{ active: activeTab === 'document' }" :class="{ active: activeTab === 'document' }"
@click="activeTab = 'document'" @click="activeTab = 'document'"
> >
@ -11,7 +10,6 @@
</button> </button>
<button <button
type="button" type="button"
class="tab"
:class="{ active: activeTab === 'code' }" :class="{ active: activeTab === 'code' }"
@click="activeTab = 'code'" @click="activeTab = 'code'"
> >
@ -19,7 +17,6 @@
</button> </button>
<button <button
type="button" type="button"
class="tab"
:class="{ active: activeTab === 'contenu' }" :class="{ active: activeTab === 'contenu' }"
@click="activeTab = 'contenu'" @click="activeTab = 'contenu'"
> >
@ -34,7 +31,7 @@
</div> </div>
<div v-else-if="activeTab === 'code'" class="tab-panel"> <div v-else-if="activeTab === 'code'" class="tab-panel">
<StylesheetViewer /> <!-- Code tab content -->
</div> </div>
<div v-else-if="activeTab === 'contenu'" class="tab-panel"> <div v-else-if="activeTab === 'contenu'" class="tab-panel">
@ -45,48 +42,43 @@
</template> </template>
<script setup> <script setup>
import { ref, provide } from 'vue'; import { ref } from 'vue';
import PageSettings from './PageSettings.vue'; import PageSettings from './PageSettings.vue';
import TextSettings from './TextSettings.vue'; import TextSettings from './TextSettings.vue';
import StylesheetViewer from '../StylesheetViewer.vue';
// Tab management // Tab management
const activeTab = ref('document'); const activeTab = ref('document');
// Provide activeTab to child components
provide('activeTab', activeTab);
</script> </script>
<style lang="scss" scoped> <style scoped>
#editor-panel { #editor-panel {
padding: 1rem;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
width: 30rem; width: 250px;
height: 100vh; height: 100vh;
display: flex; background: #f5f5f5;
flex-direction: column; padding: 1rem;
z-index: 2; box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
background-color: var(--color-panel-bg);
box-shadow: -5px 0px 12px;
}
nav {
margin-bottom: 2rem;
display: flex;
gap: 0.5rem;
}
.tab-content {
flex: 1;
overflow: hidden;
}
.tab-panel {
height: 100%;
overflow-y: auto; overflow-y: auto;
} }
h3 {
margin-top: 0;
}
.control {
margin-bottom: 1rem;
}
.control label {
display: block;
margin-bottom: 0.25rem;
font-size: 0.875rem;
}
.control input {
width: 80px;
padding: 0.25rem;
}
</style> </style>

View file

@ -2,7 +2,6 @@
<section class="settings-section"> <section class="settings-section">
<h2>Réglage des pages</h2> <h2>Réglage des pages</h2>
<div class="settings-subsection">
<div class="field"> <div class="field">
<label for="page-format">Format d'impression</label> <label for="page-format">Format d'impression</label>
<select id="page-format" v-model="pageFormat"> <select id="page-format" v-model="pageFormat">
@ -13,38 +12,33 @@
<option value="legal">Legal</option> <option value="legal">Legal</option>
</select> </select>
</div> </div>
</div>
<div class="settings-subsection"> <div class="field">
<div class="field field--view-only">
<label for="page-width">Largeur</label> <label for="page-width">Largeur</label>
<input <input
id="page-width" id="page-width"
type="number" type="text"
:value="parseInt(pageWidth)" :value="pageWidth"
disabled disabled
/> />
<button type="button" disabled>mm</button>
</div> </div>
<div class="field field--view-only"> <div class="field">
<label for="page-height">Hauteur</label> <label for="page-height">Hauteur</label>
<input <input
id="page-height" id="page-height"
type="number" type="text"
:value="parseInt(pageHeight)" :value="pageHeight"
disabled disabled
/> />
<button type="button" disabled>mm</button>
</div>
</div> </div>
<div class="settings-subsection margins"> <div class="subsection">
<h3>Marges</h3> <h3>Marges</h3>
<div class="field"> <div class="field">
<label for="margin-top">Haut</label> <label for="margin-top">Haut</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-top" id="margin-top"
type="number" type="number"
@ -72,7 +66,7 @@
<div class="field"> <div class="field">
<label for="margin-bottom">Bas</label> <label for="margin-bottom">Bas</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-bottom" id="margin-bottom"
type="number" type="number"
@ -100,7 +94,7 @@
<div class="field"> <div class="field">
<label for="margin-left">Gauche</label> <label for="margin-left">Gauche</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-left" id="margin-left"
type="number" type="number"
@ -128,7 +122,7 @@
<div class="field"> <div class="field">
<label for="margin-right">Droite</label> <label for="margin-right">Droite</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-right" id="margin-right"
type="number" type="number"
@ -155,18 +149,14 @@
</div> </div>
</div> </div>
<div class="settings-subsection">
<div class="field"> <div class="field">
<label for="background">Arrière-plan</label> <label for="background">Arrière-plan</label>
<div class="input-with-color"> <div class="field-with-unit">
<input <input
ref="backgroundColorInput"
type="text"
id="background" id="background"
type="text"
v-model="background.value" v-model="background.value"
data-coloris
/> />
<!-- Temporarily commented out
<div class="unit-toggle"> <div class="unit-toggle">
<button <button
type="button" type="button"
@ -183,12 +173,9 @@
hex hex
</button> </button>
</div> </div>
-->
</div>
</div> </div>
</div> </div>
<div class="settings-subsection">
<div class="field"> <div class="field">
<label for="pattern">Motif</label> <label for="pattern">Motif</label>
<select id="pattern" v-model="pattern"> <select id="pattern" v-model="pattern">
@ -198,31 +185,32 @@
<option value="grid">Grille</option> <option value="grid">Grille</option>
</select> </select>
</div> </div>
</div>
<div class="settings-subsection">
<div class="field checkbox-field"> <div class="field checkbox-field">
<input id="page-numbers" type="checkbox" v-model="pageNumbers" /> <input
id="page-numbers"
type="checkbox"
v-model="pageNumbers"
/>
<label for="page-numbers">Numéro de page</label> <label for="page-numbers">Numéro de page</label>
</div> </div>
<div class="field checkbox-field"> <div class="field checkbox-field">
<input id="running-title" type="checkbox" v-model="runningTitle" /> <input
id="running-title"
type="checkbox"
v-model="runningTitle"
/>
<label for="running-title">Titre courant</label> <label for="running-title">Titre courant</label>
</div> </div>
</div>
</section> </section>
</template> </template>
<script setup> <script setup>
import { ref, computed, watch, onMounted, inject } from 'vue'; import { ref, computed, watch, onMounted } from 'vue';
import { useStylesheetStore } from '../../stores/stylesheet'; import { useStylesheetStore } from '../../stores/stylesheet';
import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css';
const stylesheetStore = useStylesheetStore(); const stylesheetStore = useStylesheetStore();
const backgroundColorInput = ref(null);
const activeTab = inject('activeTab', ref('document'));
let isUpdatingFromStore = false; let isUpdatingFromStore = false;
let updateTimer = null; let updateTimer = null;
@ -234,7 +222,7 @@ const pageFormats = {
A5: { width: '148mm', height: '210mm' }, A5: { width: '148mm', height: '210mm' },
A3: { width: '297mm', height: '420mm' }, A3: { width: '297mm', height: '420mm' },
letter: { width: '8.5in', height: '11in' }, letter: { width: '8.5in', height: '11in' },
legal: { width: '8.5in', height: '14in' }, legal: { width: '8.5in', height: '14in' }
}; };
const pageWidth = computed(() => pageFormats[pageFormat.value].width); const pageWidth = computed(() => pageFormats[pageFormat.value].width);
@ -244,12 +232,12 @@ const margins = ref({
top: { value: 20, unit: 'mm' }, top: { value: 20, unit: 'mm' },
bottom: { value: 20, unit: 'mm' }, bottom: { value: 20, unit: 'mm' },
left: { value: 20, unit: 'mm' }, left: { value: 20, unit: 'mm' },
right: { value: 20, unit: 'mm' }, right: { value: 20, unit: 'mm' }
}); });
const background = ref({ const background = ref({
value: '', value: '',
format: 'hex', format: 'hex'
}); });
const pattern = ref(''); const pattern = ref('');
@ -261,20 +249,19 @@ const debouncedUpdate = (callback) => {
updateTimer = setTimeout(callback, 1000); updateTimer = setTimeout(callback, 1000);
}; };
const immediateUpdate = (callback) => {
callback();
};
watch(pageFormat, (newFormat) => { watch(pageFormat, (newFormat) => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
immediateUpdate(() => { debouncedUpdate(() => {
stylesheetStore.updateProperty('@page', 'size', newFormat, ''); stylesheetStore.updateProperty('@page', 'size', newFormat, '');
}); });
}); });
const updateMargins = () => { watch(margins, (newMargins) => {
const marginValue = `${margins.value.top.value}${margins.value.top.unit} ${margins.value.right.value}${margins.value.right.unit} ${margins.value.bottom.value}${margins.value.bottom.unit} ${margins.value.left.value}${margins.value.left.unit}`; if (isUpdatingFromStore) return;
debouncedUpdate(() => {
const marginValue = `${newMargins.top.value}${newMargins.top.unit} ${newMargins.right.value}${newMargins.right.unit} ${newMargins.bottom.value}${newMargins.bottom.unit} ${newMargins.left.value}${newMargins.left.unit}`;
const currentBlock = stylesheetStore.extractBlock('@page'); const currentBlock = stylesheetStore.extractBlock('@page');
const updatedBlock = currentBlock.replace( const updatedBlock = currentBlock.replace(
@ -282,88 +269,37 @@ const updateMargins = () => {
`$1${marginValue}` `$1${marginValue}`
); );
stylesheetStore.content = stylesheetStore.content.replace( stylesheetStore.content = stylesheetStore.content.replace(currentBlock, updatedBlock);
currentBlock, });
updatedBlock }, { deep: true });
);
};
// Watch margin values (number inputs) with debounce watch(background, (newBg) => {
watch( if (!newBg.value) return;
() => [
margins.value.top.value,
margins.value.bottom.value,
margins.value.left.value,
margins.value.right.value,
],
() => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
debouncedUpdate(updateMargins);
}
);
// Watch margin units (button clicks) without debounce
watch(
() => [
margins.value.top.unit,
margins.value.bottom.unit,
margins.value.left.unit,
margins.value.right.unit,
],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(updateMargins);
}
);
const updateBackground = () => {
if (!background.value.value) return;
debouncedUpdate(() => {
const currentBlock = stylesheetStore.extractBlock('@page'); const currentBlock = stylesheetStore.extractBlock('@page');
if (currentBlock.includes('background:')) { if (currentBlock.includes('background:')) {
const updatedBlock = currentBlock.replace( const updatedBlock = currentBlock.replace(
/(background:\s*)[^;]+/, /(background:\s*)[^;]+/,
`$1${background.value.value}` `$1${newBg.value}`
);
stylesheetStore.content = stylesheetStore.content.replace(
currentBlock,
updatedBlock
); );
stylesheetStore.content = stylesheetStore.content.replace(currentBlock, updatedBlock);
} else { } else {
const updatedBlock = currentBlock.replace( const updatedBlock = currentBlock.replace(
/(\s*})$/, /(\s*})$/,
` background: ${background.value.value};\n$1` ` background: ${newBg.value};\n$1`
);
stylesheetStore.content = stylesheetStore.content.replace(
currentBlock,
updatedBlock
); );
stylesheetStore.content = stylesheetStore.content.replace(currentBlock, updatedBlock);
} }
}; });
}, { deep: true });
// Watch background value (text input) with debounce
watch(
() => background.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(updateBackground);
}
);
// Watch background format (button clicks) without debounce
watch(
() => background.value.format,
() => {
if (isUpdatingFromStore) return;
immediateUpdate(updateBackground);
}
);
watch(pattern, (newPattern) => { watch(pattern, (newPattern) => {
if (!newPattern || isUpdatingFromStore) return; if (!newPattern || isUpdatingFromStore) return;
immediateUpdate(() => { debouncedUpdate(() => {
// TODO: implement pattern application // TODO: implement pattern application
}); });
}); });
@ -371,7 +307,7 @@ watch(pattern, (newPattern) => {
watch(pageNumbers, (enabled) => { watch(pageNumbers, (enabled) => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
immediateUpdate(() => { debouncedUpdate(() => {
// TODO: implement page numbers toggle // TODO: implement page numbers toggle
}); });
}); });
@ -379,7 +315,7 @@ watch(pageNumbers, (enabled) => {
watch(runningTitle, (enabled) => { watch(runningTitle, (enabled) => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
immediateUpdate(() => { debouncedUpdate(() => {
// TODO: implement running title toggle // TODO: implement running title toggle
}); });
}); });
@ -395,26 +331,12 @@ const syncFromStore = () => {
pageFormat.value = sizeMatch[1]; pageFormat.value = sizeMatch[1];
} }
const marginMatch = pageBlock.match( const marginMatch = pageBlock.match(/margin:\s*([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)/i);
/margin:\s*([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)\s+([0-9.]+)([a-z]+)/i
);
if (marginMatch) { if (marginMatch) {
margins.value.top = { margins.value.top = { value: parseFloat(marginMatch[1]), unit: marginMatch[2] };
value: parseFloat(marginMatch[1]), margins.value.right = { value: parseFloat(marginMatch[3]), unit: marginMatch[4] };
unit: marginMatch[2], margins.value.bottom = { value: parseFloat(marginMatch[5]), unit: marginMatch[6] };
}; margins.value.left = { value: parseFloat(marginMatch[7]), unit: marginMatch[8] };
margins.value.right = {
value: parseFloat(marginMatch[3]),
unit: marginMatch[4],
};
margins.value.bottom = {
value: parseFloat(marginMatch[5]),
unit: marginMatch[6],
};
margins.value.left = {
value: parseFloat(marginMatch[7]),
unit: marginMatch[8],
};
} }
const bgMatch = pageBlock.match(/background:\s*([^;]+)/); const bgMatch = pageBlock.match(/background:\s*([^;]+)/);
@ -426,67 +348,13 @@ const syncFromStore = () => {
} }
}; };
watch( watch(() => stylesheetStore.content, () => {
() => stylesheetStore.content,
() => {
if (!isUpdatingFromStore) { if (!isUpdatingFromStore) {
syncFromStore(); syncFromStore();
} }
}
);
const updateColorisButton = () => {
const input = backgroundColorInput.value;
if (input && background.value.value) {
// Force Coloris to update by triggering a change event
const event = new Event('input', { bubbles: true });
input.dispatchEvent(event);
}
};
// Watch for when the user returns to the "document" tab
watch(activeTab, (newTab, oldTab) => {
if (newTab === 'document' && oldTab !== 'document' && background.value.value) {
// Small delay to ensure DOM is ready
setTimeout(updateColorisButton, 100);
}
}); });
onMounted(() => { onMounted(() => {
syncFromStore(); syncFromStore();
// Initialize Coloris
Coloris.init();
Coloris({
el: '[data-coloris]',
theme: 'pill',
themeMode: 'dark',
formatToggle: true,
alpha: true,
closeButton: true,
closeLabel: 'Fermer',
clearButton: true,
clearLabel: 'Effacer',
swatchesOnly: false,
inline: false,
wrap: true,
swatches: [
'#264653',
'#2a9d8f',
'#e9c46a',
'#f4a261',
'#e76f51',
'#d62828',
'#023e8a',
'#0077b6',
'#ffffff',
'#000000',
],
});
// Initialize button color if value exists
if (background.value.value) {
setTimeout(updateColorisButton, 100);
}
}); });
</script> </script>

View file

@ -2,8 +2,8 @@
<section class="settings-section"> <section class="settings-section">
<h2>Réglage du texte</h2> <h2>Réglage du texte</h2>
<p class="infos"> <p class="infos">
Ces réglages s'appliquent à l'ensemble des éléments du document. Vous Ces réglages s'appliquent à l'ensemble des éléments du document.
pouvez modifier ensuite les éléments indépendamment. Vous pouvez modifier ensuite les éléments indépendamment.
</p> </p>
<div class="field"> <div class="field">
@ -17,7 +17,11 @@
<option value="Times New Roman">Times New Roman</option> <option value="Times New Roman">Times New Roman</option>
</select> </select>
<div class="field-checkbox"> <div class="field-checkbox">
<input id="text-italic" type="checkbox" v-model="italic" /> <input
id="text-italic"
type="checkbox"
v-model="italic"
/>
<label for="text-italic">Italique</label> <label for="text-italic">Italique</label>
</div> </div>
</div> </div>
@ -80,7 +84,7 @@
<div class="field"> <div class="field">
<label for="text-size-range">Taille du texte</label> <label for="text-size-range">Taille du texte</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="text-size-range" id="text-size-range"
type="range" type="range"
@ -136,7 +140,11 @@
<div class="field"> <div class="field">
<label for="text-color">Couleur</label> <label for="text-color">Couleur</label>
<div class="field-with-color"> <div class="field-with-color">
<input type="color" v-model="color.picker" class="color-picker" /> <input
type="color"
v-model="color.picker"
class="color-picker"
/>
<input <input
id="text-color" id="text-color"
type="text" type="text"
@ -220,7 +228,7 @@
<div class="field"> <div class="field">
<label for="margin-outer">Marges extérieures</label> <label for="margin-outer">Marges extérieures</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-outer" id="margin-outer"
type="number" type="number"
@ -258,7 +266,7 @@
<div v-if="marginOuterExpanded" class="subsection collapsed-section"> <div v-if="marginOuterExpanded" class="subsection collapsed-section">
<div class="field"> <div class="field">
<label for="margin-outer-top">Haut</label> <label for="margin-outer-top">Haut</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-outer-top" id="margin-outer-top"
type="number" type="number"
@ -286,7 +294,7 @@
<div class="field"> <div class="field">
<label for="margin-outer-bottom">Bas</label> <label for="margin-outer-bottom">Bas</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-outer-bottom" id="margin-outer-bottom"
type="number" type="number"
@ -314,7 +322,7 @@
<div class="field"> <div class="field">
<label for="margin-outer-left">Gauche</label> <label for="margin-outer-left">Gauche</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-outer-left" id="margin-outer-left"
type="number" type="number"
@ -342,7 +350,7 @@
<div class="field"> <div class="field">
<label for="margin-outer-right">Droite</label> <label for="margin-outer-right">Droite</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-outer-right" id="margin-outer-right"
type="number" type="number"
@ -371,7 +379,7 @@
<div class="field"> <div class="field">
<label for="margin-inner">Marges intérieures</label> <label for="margin-inner">Marges intérieures</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-inner" id="margin-inner"
type="number" type="number"
@ -409,7 +417,7 @@
<div v-if="marginInnerExpanded" class="subsection collapsed-section"> <div v-if="marginInnerExpanded" class="subsection collapsed-section">
<div class="field"> <div class="field">
<label for="margin-inner-top">Haut</label> <label for="margin-inner-top">Haut</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-inner-top" id="margin-inner-top"
type="number" type="number"
@ -437,7 +445,7 @@
<div class="field"> <div class="field">
<label for="margin-inner-bottom">Bas</label> <label for="margin-inner-bottom">Bas</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-inner-bottom" id="margin-inner-bottom"
type="number" type="number"
@ -465,7 +473,7 @@
<div class="field"> <div class="field">
<label for="margin-inner-left">Gauche</label> <label for="margin-inner-left">Gauche</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-inner-left" id="margin-inner-left"
type="number" type="number"
@ -493,7 +501,7 @@
<div class="field"> <div class="field">
<label for="margin-inner-right">Droite</label> <label for="margin-inner-right">Droite</label>
<div class="input-with-unit"> <div class="field-with-unit">
<input <input
id="margin-inner-right" id="margin-inner-right"
type="number" type="number"
@ -523,22 +531,7 @@
</template> </template>
<script setup> <script setup>
import { ref, watch } from 'vue'; import { ref } from 'vue';
import { useStylesheetStore } from '../../stores/stylesheet';
const stylesheetStore = useStylesheetStore();
let isUpdatingFromStore = false;
let updateTimer = null;
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};
// Font // Font
const font = ref('Alegreya Sans'); const font = ref('Alegreya Sans');
@ -550,7 +543,7 @@ const weight = ref('400');
// Font size // Font size
const fontSize = ref({ const fontSize = ref({
value: 23, value: 23,
unit: 'px', unit: 'px'
}); });
// Alignment // Alignment
@ -560,7 +553,7 @@ const alignment = ref('left');
const color = ref({ const color = ref({
picker: '#000000', picker: '#000000',
value: 'rgb(250, 250, 250)', value: 'rgb(250, 250, 250)',
format: 'rgb', format: 'rgb'
}); });
const clearColor = () => { const clearColor = () => {
@ -572,7 +565,7 @@ const clearColor = () => {
const background = ref({ const background = ref({
enabled: false, enabled: false,
value: 'transparent', value: 'transparent',
format: 'hex', format: 'hex'
}); });
const clearBackground = () => { const clearBackground = () => {
@ -582,7 +575,7 @@ const clearBackground = () => {
// Margin outer // Margin outer
const marginOuter = ref({ const marginOuter = ref({
value: 23, value: 23,
unit: 'mm', unit: 'mm'
}); });
const marginOuterExpanded = ref(false); const marginOuterExpanded = ref(false);
@ -591,13 +584,13 @@ const marginOuterDetailed = ref({
top: { value: 23, unit: 'mm' }, top: { value: 23, unit: 'mm' },
bottom: { value: 23, unit: 'mm' }, bottom: { value: 23, unit: 'mm' },
left: { value: 23, unit: 'mm' }, left: { value: 23, unit: 'mm' },
right: { value: 23, unit: 'mm' }, right: { value: 23, unit: 'mm' }
}); });
// Margin inner // Margin inner
const marginInner = ref({ const marginInner = ref({
value: 23, value: 23,
unit: 'mm', unit: 'mm'
}); });
const marginInnerExpanded = ref(false); const marginInnerExpanded = ref(false);
@ -606,202 +599,6 @@ const marginInnerDetailed = ref({
top: { value: 23, unit: 'mm' }, top: { value: 23, unit: 'mm' },
bottom: { value: 23, unit: 'mm' }, bottom: { value: 23, unit: 'mm' },
left: { value: 23, unit: 'mm' }, left: { value: 23, unit: 'mm' },
right: { value: 23, unit: 'mm' }, right: { value: 23, unit: 'mm' }
}); });
// Watchers - Immediate updates for select/buttons/checkboxes
watch(font, () => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement font update
});
});
watch(italic, () => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement italic update
});
});
watch(weight, () => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement weight update
});
});
watch(alignment, () => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement alignment update
});
});
// Font size - debounced for value, immediate for unit
watch(
() => fontSize.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement font size update
});
}
);
watch(
() => fontSize.value.unit,
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement font size update
});
}
);
// Color - debounced for text value, immediate for format and picker
watch(
() => color.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement color update
});
}
);
watch(
() => [color.value.format, color.value.picker],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement color update
});
}
);
// Background - debounced for value, immediate for format and enabled
watch(
() => background.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement background update
});
}
);
watch(
() => [background.value.format, background.value.enabled],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement background update
});
}
);
// Margin outer - debounced for value, immediate for unit
watch(
() => marginOuter.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement margin outer update
});
}
);
watch(
() => marginOuter.value.unit,
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement margin outer update
});
}
);
// Margin outer detailed - debounced for values, immediate for units
watch(
() => [
marginOuterDetailed.value.top.value,
marginOuterDetailed.value.bottom.value,
marginOuterDetailed.value.left.value,
marginOuterDetailed.value.right.value,
],
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement margin outer detailed update
});
}
);
watch(
() => [
marginOuterDetailed.value.top.unit,
marginOuterDetailed.value.bottom.unit,
marginOuterDetailed.value.left.unit,
marginOuterDetailed.value.right.unit,
],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement margin outer detailed update
});
}
);
// Margin inner - debounced for value, immediate for unit
watch(
() => marginInner.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement margin inner update
});
}
);
watch(
() => marginInner.value.unit,
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement margin inner update
});
}
);
// Margin inner detailed - debounced for values, immediate for units
watch(
() => [
marginInnerDetailed.value.top.value,
marginInnerDetailed.value.bottom.value,
marginInnerDetailed.value.left.value,
marginInnerDetailed.value.right.value,
],
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
// TODO: implement margin inner detailed update
});
}
);
watch(
() => [
marginInnerDetailed.value.top.unit,
marginInnerDetailed.value.bottom.unit,
marginInnerDetailed.value.left.unit,
marginInnerDetailed.value.right.unit,
],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement margin inner detailed update
});
}
);
</script> </script>

View file

@ -1 +1,2 @@
@import url('/assets/css/style.css'); @import url('/assets/css/pagedjs-interface.css');
@import url('/assets/css/editor-ui.css');