944 lines
33 KiB
HTML
944 lines
33 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Application de Mots Fléchés</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f3f4f6;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
h2 {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.setup-screen {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.setup-box {
|
||
background: white;
|
||
padding: 32px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
input[type="number"], input[type="text"], textarea {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
textarea {
|
||
font-family: monospace;
|
||
resize: vertical;
|
||
}
|
||
|
||
button {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: #2563eb;
|
||
color: white;
|
||
width: 100%;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background-color: #1d4ed8;
|
||
}
|
||
|
||
.btn-success {
|
||
background-color: #16a34a;
|
||
color: white;
|
||
}
|
||
|
||
.btn-success:hover {
|
||
background-color: #15803d;
|
||
}
|
||
|
||
.btn-danger {
|
||
background-color: #dc2626;
|
||
color: white;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background-color: #b91c1c;
|
||
}
|
||
|
||
.btn-disabled {
|
||
background-color: #9ca3af;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.card {
|
||
background: white;
|
||
padding: 16px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.grid-screen {
|
||
display: none;
|
||
}
|
||
|
||
.grid-container {
|
||
background: white;
|
||
padding: 16px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
display: inline-block;
|
||
}
|
||
|
||
.grid {
|
||
display: inline-block;
|
||
position: relative;
|
||
}
|
||
|
||
.grid-row {
|
||
display: flex;
|
||
}
|
||
|
||
.cell {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 1px solid #6b7280;
|
||
position: relative;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.cell.letter {
|
||
background-color: white;
|
||
}
|
||
|
||
.cell.definition {
|
||
background-color: #d1d5db;
|
||
}
|
||
|
||
.cell.active {
|
||
outline: 2px solid #2563eb;
|
||
outline-offset: -2px;
|
||
z-index: 10;
|
||
}
|
||
|
||
.cell.highlighted {
|
||
background-color: #dbeafe;
|
||
}
|
||
|
||
.cell-content {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.definition-text {
|
||
position: relative;
|
||
padding: 2px;
|
||
font-size: 5px;
|
||
line-height: 6px;
|
||
overflow: hidden;
|
||
height: 100%;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
.definition-split {
|
||
display: flex;
|
||
position: absolute;
|
||
inset: 0;
|
||
}
|
||
|
||
.definition-half {
|
||
width: 50%;
|
||
position: relative;
|
||
padding: 2px;
|
||
font-size: 5px;
|
||
line-height: 6px;
|
||
overflow: hidden;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
.definition-half:first-child {
|
||
border-right: 1px solid #6b7280;
|
||
}
|
||
|
||
/* Visual hint for hyphens: dotted border between cells */
|
||
.cell.hyphen {
|
||
border-right: 2px dotted #000;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.arrow {
|
||
position: absolute;
|
||
bottom: 0;
|
||
right: 0;
|
||
}
|
||
|
||
.controls {
|
||
display: flex;
|
||
gap: 16px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.add-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.add-btn, .remove-btn {
|
||
width: 20px;
|
||
height: 20px;
|
||
padding: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #e5e7eb;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.add-btn:hover {
|
||
background-color: #d1d5db;
|
||
}
|
||
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
z-index: 1000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal.show {
|
||
display: flex;
|
||
}
|
||
|
||
.modal-content {
|
||
background: white;
|
||
padding: 24px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.15);
|
||
max-width: 600px;
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.modal-content h3 {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.definition-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.definition-item {
|
||
width: 100%;
|
||
text-align: left;
|
||
padding: 12px;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 4px;
|
||
background: white;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.definition-item:hover {
|
||
background-color: #dbeafe;
|
||
}
|
||
|
||
.add-row-controls {
|
||
display: flex;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.add-col-control {
|
||
display: flex;
|
||
justify-content: center;
|
||
width: 40px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Écran de configuration -->
|
||
<div id="setupScreen" class="setup-screen">
|
||
<div class="setup-box">
|
||
<h1>Créer une grille de mots fléchés</h1>
|
||
<div class="form-group">
|
||
<label>Largeur (colonnes)</label>
|
||
<input type="number" id="widthInput" value="10" min="3" max="30">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Hauteur (lignes)</label>
|
||
<input type="number" id="heightInput" value="10" min="3" max="30">
|
||
</div>
|
||
<button class="btn-primary" onclick="startGrid()">Commencer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Écran de grille -->
|
||
<div id="gridScreen" class="grid-screen">
|
||
<div class="container">
|
||
<h1>Éditeur de mots fléchés</h1>
|
||
|
||
<div class="card">
|
||
<h2>Charger un dictionnaire</h2>
|
||
<div class="form-group">
|
||
<input type="text" id="dictionaryUrl" placeholder="Ou entrez une URL pour charger un dictionnaire (raw .txt)" style="width:100%; padding:8px 12px; border:1px solid #d1d5db; border-radius:4px; margin-bottom:8px;">
|
||
<textarea id="dictionaryText" rows="5" placeholder="Collez le contenu du dictionnaire ici (format: mot: définition)"></textarea>
|
||
</div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;">
|
||
<button class="btn-success" onclick="loadDictionaryFromTextarea()">Charger depuis zone</button>
|
||
<button class="btn-primary" onclick="loadDictionaryFromUrl()">Charger depuis URL</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid-container">
|
||
<div id="grid" class="grid"></div>
|
||
<div class="controls">
|
||
<button class="btn-danger" onclick="removeRow()">− Supprimer ligne</button>
|
||
<button class="btn-danger" onclick="removeColumn()">− Supprimer colonne</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal de sélection de définition -->
|
||
<div id="definitionModal" class="modal">
|
||
<div class="modal-content">
|
||
<h3 id="modalTitle">Définitions</h3>
|
||
<div id="definitionList" class="definition-list"></div>
|
||
<button class="btn-primary" onclick="closeModal()">Fermer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// État de l'application
|
||
let grid = [];
|
||
let activeCell = null;
|
||
let orientation = 'horizontal';
|
||
let dictionaries = {};
|
||
let currentModal = null;
|
||
|
||
const CELL_SIZE = 40;
|
||
|
||
// Normalise un mot (majuscules, sans accents)
|
||
function normalizeWord(word) {
|
||
return word.toUpperCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||
}
|
||
|
||
// Crée une grille vide
|
||
function createGrid(width, height) {
|
||
const newGrid = [];
|
||
for (let row = 0; row < height; row++) {
|
||
const rowData = [];
|
||
for (let col = 0; col < width; col++) {
|
||
// indices are 1-based in spec: definition if on first row or first column and index is odd (1-based)
|
||
const isDefinition = ((row === 0 && ((col + 1) % 2 === 1)) || (col === 0 && ((row + 1) % 2 === 1)));
|
||
rowData.push({
|
||
type: isDefinition ? 'definition' : 'letter',
|
||
letter: '',
|
||
definitionH: '',
|
||
definitionV: ''
|
||
});
|
||
}
|
||
newGrid.push(rowData);
|
||
}
|
||
return newGrid;
|
||
}
|
||
|
||
// Démarre la grille
|
||
function startGrid() {
|
||
const width = parseInt(document.getElementById('widthInput').value);
|
||
const height = parseInt(document.getElementById('heightInput').value);
|
||
|
||
grid = createGrid(width, height);
|
||
// Ensure the first-row/first-column odd-index rule is strictly applied
|
||
updateDefinitionTypes();
|
||
|
||
document.getElementById('setupScreen').style.display = 'none';
|
||
document.getElementById('gridScreen').style.display = 'block';
|
||
|
||
renderGrid();
|
||
}
|
||
|
||
// Re-evaluate and enforce first-row/first-column definition rule after structural changes
|
||
function updateDefinitionTypes() {
|
||
for (let r = 0; r < grid.length; r++) {
|
||
for (let c = 0; c < grid[0].length; c++) {
|
||
const onFirstRowRule = (r === 0 && ((c + 1) % 2 === 1));
|
||
const onFirstColRule = (c === 0 && ((r + 1) % 2 === 1));
|
||
if (onFirstRowRule || onFirstColRule) {
|
||
grid[r][c].type = 'definition';
|
||
} else if (r === 0 || c === 0) {
|
||
// if on first row/col but not meeting the odd-index rule, ensure it's a letter
|
||
grid[r][c].type = 'letter';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Obtient le mot à une position donnée
|
||
function getWordAt(row, col, dir) {
|
||
if (!grid[row] || !grid[row][col]) return { word: '', startRow: row, startCol: col };
|
||
|
||
let word = '';
|
||
let startRow = row;
|
||
let startCol = col;
|
||
|
||
// Trouve le début du mot
|
||
if (dir === 'horizontal') {
|
||
while (startCol > 0 && grid[startRow][startCol - 1]?.type === 'letter' && grid[startRow][startCol - 1]?.letter) {
|
||
startCol--;
|
||
}
|
||
// Construit le mot
|
||
let c = startCol;
|
||
while (c < grid[0].length && grid[startRow][c]?.type === 'letter' && grid[startRow][c]?.letter) {
|
||
word += grid[startRow][c].letter;
|
||
c++;
|
||
}
|
||
} else {
|
||
while (startRow > 0 && grid[startRow - 1][startCol]?.type === 'letter' && grid[startRow - 1][startCol]?.letter) {
|
||
startRow--;
|
||
}
|
||
// Construit le mot
|
||
let r = startRow;
|
||
while (r < grid.length && grid[r][startCol]?.type === 'letter' && grid[r][startCol]?.letter) {
|
||
word += grid[r][startCol].letter;
|
||
r++;
|
||
}
|
||
}
|
||
|
||
return { word, startRow, startCol };
|
||
}
|
||
|
||
// Gère le clic sur une case
|
||
function handleCellClick(row, col, half = null) {
|
||
const cell = grid[row][col];
|
||
|
||
if (cell.type === 'definition') {
|
||
// Determine associated words in both directions (starting at the neighboring letter cell)
|
||
const wordInfoH = getWordAt(row, col + 1, 'horizontal');
|
||
const wordInfoV = getWordAt(row + 1, col, 'vertical');
|
||
|
||
let targetWord = null;
|
||
let targetDir = null;
|
||
|
||
if (half === 'h') {
|
||
targetWord = wordInfoH;
|
||
targetDir = 'horizontal';
|
||
} else if (half === 'v') {
|
||
targetWord = wordInfoV;
|
||
targetDir = 'vertical';
|
||
} else {
|
||
// if no half specified, pick the direction that actually has a word
|
||
if (wordInfoH && wordInfoH.word) {
|
||
targetWord = wordInfoH;
|
||
targetDir = 'horizontal';
|
||
} else if (wordInfoV && wordInfoV.word) {
|
||
targetWord = wordInfoV;
|
||
targetDir = 'vertical';
|
||
}
|
||
}
|
||
|
||
if (targetWord && targetWord.word) {
|
||
const normalized = normalizeWord(targetWord.word);
|
||
const defs = dictionaries[normalized] || [];
|
||
const uniqueDefs = [...new Set(defs)];
|
||
|
||
currentModal = { row, col, half, direction: targetDir };
|
||
showDefinitions(targetWord.word, uniqueDefs);
|
||
}
|
||
} else {
|
||
if (activeCell && activeCell.row === row && activeCell.col === col) {
|
||
orientation = orientation === 'horizontal' ? 'vertical' : 'horizontal';
|
||
} else {
|
||
activeCell = { row, col };
|
||
}
|
||
renderGrid();
|
||
}
|
||
}
|
||
|
||
// Gère le double-clic
|
||
function handleCellDoubleClick(row, col) {
|
||
const cell = grid[row][col];
|
||
cell.type = cell.type === 'letter' ? 'definition' : 'letter';
|
||
if (cell.type === 'letter') {
|
||
cell.definitionH = '';
|
||
cell.definitionV = '';
|
||
} else {
|
||
cell.letter = '';
|
||
}
|
||
renderGrid();
|
||
}
|
||
|
||
// Affiche les définitions
|
||
function showDefinitions(word, definitions) {
|
||
document.getElementById('modalTitle').textContent = `Définitions pour "${word}"`;
|
||
const list = document.getElementById('definitionList');
|
||
list.innerHTML = '';
|
||
|
||
if (definitions.length === 0) {
|
||
list.innerHTML = '<p style="color: #6b7280;">Aucune définition disponible pour ce mot.</p>';
|
||
} else {
|
||
definitions.forEach(def => {
|
||
const btn = document.createElement('button');
|
||
btn.className = 'definition-item';
|
||
btn.textContent = def;
|
||
btn.onclick = () => selectDefinition(def);
|
||
list.appendChild(btn);
|
||
});
|
||
}
|
||
|
||
document.getElementById('definitionModal').classList.add('show');
|
||
}
|
||
|
||
// Sélectionne une définition
|
||
function selectDefinition(definition) {
|
||
if (!currentModal) return;
|
||
|
||
const { row, col, direction } = currentModal;
|
||
|
||
if (direction === 'horizontal') {
|
||
grid[row][col].definitionH = definition;
|
||
} else {
|
||
grid[row][col].definitionV = definition;
|
||
}
|
||
|
||
closeModal();
|
||
renderGrid();
|
||
}
|
||
|
||
// Ferme le modal
|
||
function closeModal() {
|
||
document.getElementById('definitionModal').classList.remove('show');
|
||
currentModal = null;
|
||
}
|
||
|
||
// Charge un dictionnaire
|
||
// Helper to process a single line from a dictionary source
|
||
function processDictionaryLine(line) {
|
||
const match = line.match(/^(.+?):\s*(.+)$/);
|
||
if (match) {
|
||
const word = normalizeWord(match[1].trim());
|
||
const definition = match[2].trim();
|
||
|
||
if (!dictionaries[word]) {
|
||
dictionaries[word] = [];
|
||
}
|
||
dictionaries[word].push(definition);
|
||
}
|
||
}
|
||
|
||
// Load from the textarea (legacy/paste)
|
||
function loadDictionaryFromTextarea() {
|
||
const text = document.getElementById('dictionaryText').value;
|
||
if (!text.trim()) {
|
||
alert('Aucun contenu dans la zone de texte.');
|
||
return;
|
||
}
|
||
dictionaries = {};
|
||
const lines = text.split('\n');
|
||
for (const line of lines) {
|
||
processDictionaryLine(line);
|
||
}
|
||
alert('Dictionnaire chargé avec succès !');
|
||
}
|
||
|
||
// Load a potentially very large dictionary from a URL using streaming
|
||
async function loadDictionaryFromUrl() {
|
||
const url = document.getElementById('dictionaryUrl').value.trim();
|
||
if (!url) {
|
||
alert('Entrez une URL valide.');
|
||
return;
|
||
}
|
||
|
||
dictionaries = {};
|
||
try {
|
||
const resp = await fetch(url);
|
||
if (!resp.ok || !resp.body) {
|
||
alert('Impossible de récupérer le fichier depuis l\'URL fournie.');
|
||
return;
|
||
}
|
||
|
||
const reader = resp.body.getReader();
|
||
const decoder = new TextDecoder();
|
||
let { value, done } = await reader.read();
|
||
let chunk = '';
|
||
while (!done) {
|
||
chunk += decoder.decode(value, { stream: true });
|
||
const lines = chunk.split(/\r?\n/);
|
||
chunk = lines.pop();
|
||
for (const line of lines) {
|
||
processDictionaryLine(line);
|
||
}
|
||
({ value, done } = await reader.read());
|
||
}
|
||
if (chunk) processDictionaryLine(chunk);
|
||
|
||
alert('Dictionnaire chargé depuis URL avec succès !');
|
||
} catch (err) {
|
||
console.error(err);
|
||
alert('Erreur lors du chargement du dictionnaire.');
|
||
}
|
||
}
|
||
|
||
// Ajoute une ligne
|
||
function addRow() {
|
||
const rowIndex = grid.length;
|
||
const newRow = [];
|
||
|
||
for (let col = 0; col < grid[0].length; col++) {
|
||
const isDefinition = (col === 0 && ((rowIndex + 1) % 2 === 1));
|
||
newRow.push({
|
||
type: isDefinition ? 'definition' : 'letter',
|
||
letter: '',
|
||
definitionH: '',
|
||
definitionV: ''
|
||
});
|
||
}
|
||
|
||
grid.push(newRow);
|
||
// Re-evaluate first row/column rule strictly after structural change
|
||
updateDefinitionTypes();
|
||
renderGrid();
|
||
}
|
||
|
||
// Ajoute une colonne
|
||
function addColumn() {
|
||
grid.forEach((row, rowIndex) => {
|
||
const colIndex = row.length;
|
||
const isDefinition = (rowIndex === 0 && ((colIndex + 1) % 2 === 1));
|
||
row.push({
|
||
type: isDefinition ? 'definition' : 'letter',
|
||
letter: '',
|
||
definitionH: '',
|
||
definitionV: ''
|
||
});
|
||
});
|
||
updateDefinitionTypes();
|
||
renderGrid();
|
||
}
|
||
|
||
// Supprime une ligne
|
||
function removeRow() {
|
||
if (grid.length > 1) {
|
||
const lastRowIndex = grid.length - 1;
|
||
const rowToCheck = grid[lastRowIndex];
|
||
let hasContent = false;
|
||
for (const cell of rowToCheck) {
|
||
if ((cell.type === 'letter' && cell.letter) || (cell.type === 'definition' && (cell.definitionH || cell.definitionV))) {
|
||
hasContent = true;
|
||
break;
|
||
}
|
||
}
|
||
if (hasContent) {
|
||
const ok = confirm('La ligne contient des lettres ou des définitions. Confirmer la suppression ?');
|
||
if (!ok) return;
|
||
}
|
||
|
||
grid.pop();
|
||
if (activeCell && activeCell.row >= grid.length) {
|
||
activeCell = null;
|
||
}
|
||
updateDefinitionTypes();
|
||
renderGrid();
|
||
}
|
||
}
|
||
|
||
// Supprime une colonne
|
||
function removeColumn() {
|
||
if (grid[0].length > 1) {
|
||
const lastColIndex = grid[0].length - 1;
|
||
let hasContent = false;
|
||
for (const row of grid) {
|
||
const cell = row[lastColIndex];
|
||
if ((cell.type === 'letter' && cell.letter) || (cell.type === 'definition' && (cell.definitionH || cell.definitionV))) {
|
||
hasContent = true;
|
||
break;
|
||
}
|
||
}
|
||
if (hasContent) {
|
||
const ok = confirm('La colonne contient des lettres ou des définitions. Confirmer la suppression ?');
|
||
if (!ok) return;
|
||
}
|
||
|
||
grid.forEach(row => row.pop());
|
||
if (activeCell && activeCell.col >= grid[0].length) {
|
||
activeCell = null;
|
||
}
|
||
updateDefinitionTypes();
|
||
renderGrid();
|
||
}
|
||
}
|
||
|
||
// Rend la grille
|
||
function renderGrid() {
|
||
const gridEl = document.getElementById('grid');
|
||
gridEl.innerHTML = '';
|
||
|
||
grid.forEach((row, rowIndex) => {
|
||
const rowEl = document.createElement('div');
|
||
rowEl.className = 'grid-row';
|
||
|
||
row.forEach((cell, colIndex) => {
|
||
const cellEl = document.createElement('div');
|
||
cellEl.className = 'cell ' + cell.type;
|
||
|
||
const isActive = activeCell?.row === rowIndex && activeCell?.col === colIndex;
|
||
const isHighlighted = activeCell && cell.type === 'letter' && (
|
||
(orientation === 'horizontal' && activeCell.row === rowIndex) ||
|
||
(orientation === 'vertical' && activeCell.col === colIndex)
|
||
);
|
||
|
||
if (isActive) cellEl.classList.add('active');
|
||
if (isHighlighted) cellEl.classList.add('highlighted');
|
||
|
||
let hasBoth = false;
|
||
|
||
if (cell.type === 'letter') {
|
||
const content = document.createElement('div');
|
||
content.className = 'cell-content';
|
||
content.textContent = cell.letter;
|
||
cellEl.appendChild(content);
|
||
// If the letter is a hyphen, add visual dotted border indication
|
||
if (cell.letter === '-') {
|
||
cellEl.classList.add('hyphen');
|
||
}
|
||
} else {
|
||
hasBoth = cell.definitionH && cell.definitionV;
|
||
|
||
if (hasBoth) {
|
||
const split = document.createElement('div');
|
||
split.className = 'definition-split';
|
||
|
||
const halfH = document.createElement('div');
|
||
halfH.className = 'definition-half';
|
||
halfH.textContent = cell.definitionH;
|
||
halfH.innerHTML += '<svg class="arrow" width="8" height="8" viewBox="0 0 8 8"><path d="M 2 4 L 6 4 M 6 4 L 4 2 M 6 4 L 4 6" stroke="black" stroke-width="1" fill="none"/></svg>';
|
||
|
||
const halfV = document.createElement('div');
|
||
halfV.className = 'definition-half';
|
||
halfV.textContent = cell.definitionV;
|
||
halfV.innerHTML += '<svg class="arrow" width="8" height="8" viewBox="0 0 8 8"><path d="M 4 2 L 4 6 M 4 6 L 2 4 M 4 6 L 6 4" stroke="black" stroke-width="1" fill="none"/></svg>';
|
||
|
||
split.appendChild(halfH);
|
||
split.appendChild(halfV);
|
||
cellEl.appendChild(split);
|
||
|
||
halfH.onclick = (e) => {
|
||
e.stopPropagation();
|
||
handleCellClick(rowIndex, colIndex, 'h');
|
||
};
|
||
halfV.onclick = (e) => {
|
||
e.stopPropagation();
|
||
handleCellClick(rowIndex, colIndex, 'v');
|
||
};
|
||
} else {
|
||
const text = document.createElement('div');
|
||
text.className = 'definition-text';
|
||
text.textContent = cell.definitionH || cell.definitionV;
|
||
|
||
if (cell.definitionH || cell.definitionV) {
|
||
if (cell.definitionV) {
|
||
text.innerHTML += '<svg class="arrow" width="8" height="8" viewBox="0 0 8 8"><path d="M 4 2 L 4 6 M 4 6 L 2 4 M 4 6 L 6 4" stroke="black" stroke-width="1" fill="none"/></svg>';
|
||
} else {
|
||
text.innerHTML += '<svg class="arrow" width="8" height="8" viewBox="0 0 8 8"><path d="M 2 4 L 6 4 M 6 4 L 4 2 M 6 4 L 4 6" stroke="black" stroke-width="1" fill="none"/></svg>';
|
||
}
|
||
}
|
||
|
||
cellEl.appendChild(text);
|
||
}
|
||
}
|
||
|
||
if (!hasBoth) {
|
||
cellEl.onclick = () => handleCellClick(rowIndex, colIndex);
|
||
}
|
||
cellEl.ondblclick = () => handleCellDoubleClick(rowIndex, colIndex);
|
||
|
||
rowEl.appendChild(cellEl);
|
||
});
|
||
|
||
// Bouton d'ajout de colonne
|
||
const addColBtn = document.createElement('div');
|
||
addColBtn.className = 'add-controls';
|
||
addColBtn.innerHTML = '<button class="add-btn" onclick="addColumn()">+</button>';
|
||
rowEl.appendChild(addColBtn);
|
||
|
||
gridEl.appendChild(rowEl);
|
||
});
|
||
|
||
// Ligne d'ajout
|
||
const addRowLine = document.createElement('div');
|
||
addRowLine.className = 'add-row-controls';
|
||
for (let i = 0; i < grid[0].length; i++) {
|
||
const addBtn = document.createElement('div');
|
||
addBtn.className = 'add-col-control';
|
||
addBtn.innerHTML = '<button class="add-btn" onclick="addRow()">+</button>';
|
||
addRowLine.appendChild(addBtn);
|
||
}
|
||
gridEl.appendChild(addRowLine);
|
||
}
|
||
|
||
// Gestion du clavier
|
||
document.addEventListener('keydown', (e) => {
|
||
if (!activeCell || document.getElementById('gridScreen').style.display === 'none') return;
|
||
|
||
const { row, col } = activeCell;
|
||
|
||
if (e.key === 'Tab') {
|
||
e.preventDefault();
|
||
orientation = orientation === 'horizontal' ? 'vertical' : 'horizontal';
|
||
renderGrid();
|
||
return;
|
||
}
|
||
|
||
if (e.key === 'Backspace') {
|
||
e.preventDefault();
|
||
if (grid[row][col].letter) {
|
||
grid[row][col].letter = '';
|
||
renderGrid();
|
||
} else {
|
||
if (orientation === 'horizontal' && col > 0) {
|
||
const prevCol = col - 1;
|
||
if (grid[row][prevCol].type === 'letter') {
|
||
grid[row][prevCol].letter = '';
|
||
activeCell = { row, col: prevCol };
|
||
renderGrid();
|
||
}
|
||
} else if (orientation === 'vertical' && row > 0) {
|
||
const prevRow = row - 1;
|
||
if (grid[prevRow][col].type === 'letter') {
|
||
grid[prevRow][col].letter = '';
|
||
activeCell = { row: prevRow, col };
|
||
renderGrid();
|
||
}
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (e.key.startsWith('Arrow')) {
|
||
e.preventDefault();
|
||
let newRow = row;
|
||
let newCol = col;
|
||
|
||
if (e.key === 'ArrowUp') newRow = Math.max(0, row - 1);
|
||
if (e.key === 'ArrowDown') newRow = Math.min(grid.length - 1, row + 1);
|
||
if (e.key === 'ArrowLeft') newCol = Math.max(0, col - 1);
|
||
if (e.key === 'ArrowRight') newCol = Math.min(grid[0].length - 1, col + 1);
|
||
|
||
activeCell = { row: newRow, col: newCol };
|
||
renderGrid();
|
||
return;
|
||
}
|
||
|
||
if (e.key.length === 1 && /[a-zA-Z]/.test(e.key)) {
|
||
e.preventDefault();
|
||
|
||
if (grid[row][col].type !== 'letter') return;
|
||
|
||
// Crée automatiquement une case définition si nécessaire
|
||
if (orientation === 'horizontal' && col > 0) {
|
||
const prevCell = grid[row][col - 1];
|
||
if (prevCell.type === 'letter' && !prevCell.letter) {
|
||
prevCell.type = 'definition';
|
||
}
|
||
} else if (orientation === 'vertical' && row > 0) {
|
||
const prevCell = grid[row - 1][col];
|
||
if (prevCell.type === 'letter' && !prevCell.letter) {
|
||
prevCell.type = 'definition';
|
||
}
|
||
}
|
||
|
||
grid[row][col].letter = e.key.toUpperCase();
|
||
|
||
// Avance à la case suivante
|
||
if (orientation === 'horizontal' && col < grid[0].length - 1) {
|
||
let nextCol = col + 1;
|
||
while (nextCol < grid[0].length && grid[row][nextCol].type !== 'letter') {
|
||
nextCol++;
|
||
}
|
||
if (nextCol < grid[0].length) {
|
||
activeCell = { row, col: nextCol };
|
||
}
|
||
} else if (orientation === 'vertical' && row < grid.length - 1) {
|
||
let nextRow = row + 1;
|
||
while (nextRow < grid.length && grid[nextRow][col].type !== 'letter') {
|
||
nextRow++;
|
||
}
|
||
if (nextRow < grid.length) {
|
||
activeCell = { row: nextRow, col };
|
||
}
|
||
}
|
||
|
||
renderGrid();
|
||
}
|
||
});
|
||
|
||
// Ensure tab toggles orientation even if focus is elsewhere
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Tab') {
|
||
e.preventDefault();
|
||
orientation = orientation === 'horizontal' ? 'vertical' : 'horizontal';
|
||
renderGrid();
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |