init2
This commit is contained in:
parent
4e3e22b175
commit
d8f3fc991d
328
index.html
328
index.html
@ -340,11 +340,11 @@
|
|||||||
<h2>Charger un dictionnaire</h2>
|
<h2>Charger un dictionnaire</h2>
|
||||||
<div class="form-group">
|
<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;">
|
<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;">
|
||||||
|
<div style="margin-top:8px;margin-bottom:8px;"><button class="btn-primary" onclick="loadDictionaryFromUrl()">Charger depuis URL</button></div>
|
||||||
<textarea id="dictionaryText" rows="5" placeholder="Collez le contenu du dictionnaire ici (format: mot: définition)"></textarea>
|
<textarea id="dictionaryText" rows="5" placeholder="Collez le contenu du dictionnaire ici (format: mot: définition)"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;">
|
<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-success" onclick="loadDictionaryFromTextarea()">Charger depuis zone</button>
|
||||||
<button class="btn-primary" onclick="loadDictionaryFromUrl()">Charger depuis URL</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -473,37 +473,36 @@
|
|||||||
|
|
||||||
if (cell.type === 'definition') {
|
if (cell.type === 'definition') {
|
||||||
// Determine associated words in both directions (starting at the neighboring letter cell)
|
// Determine associated words in both directions (starting at the neighboring letter cell)
|
||||||
const wordInfoH = getWordAt(row, col + 1, 'horizontal');
|
// Special rules for top row and left column mappings
|
||||||
const wordInfoV = getWordAt(row + 1, col, 'vertical');
|
let wordInfoH;
|
||||||
|
let wordInfoV;
|
||||||
|
|
||||||
let targetWord = null;
|
// Horizontal definition normally maps to word starting at (row, col+1) horizontally,
|
||||||
let targetDir = null;
|
// but if this definition is on the top row, its horizontal definition maps to the vertical word starting at (row, col+1).
|
||||||
|
if (row === 0) {
|
||||||
if (half === 'h') {
|
wordInfoH = getWordAt(row, col + 1, 'vertical');
|
||||||
targetWord = wordInfoH;
|
|
||||||
targetDir = 'horizontal';
|
|
||||||
} else if (half === 'v') {
|
|
||||||
targetWord = wordInfoV;
|
|
||||||
targetDir = 'vertical';
|
|
||||||
} else {
|
} else {
|
||||||
// if no half specified, pick the direction that actually has a word
|
wordInfoH = getWordAt(row, col + 1, 'horizontal');
|
||||||
if (wordInfoH && wordInfoH.word) {
|
|
||||||
targetWord = wordInfoH;
|
|
||||||
targetDir = 'horizontal';
|
|
||||||
} else if (wordInfoV && wordInfoV.word) {
|
|
||||||
targetWord = wordInfoV;
|
|
||||||
targetDir = 'vertical';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetWord && targetWord.word) {
|
// Vertical definition normally maps to word starting at (row+1, col) vertically,
|
||||||
const normalized = normalizeWord(targetWord.word);
|
// but if this definition is on the first column, its vertical definition maps to the horizontal word starting at (row+1, col).
|
||||||
const defs = dictionaries[normalized] || [];
|
if (col === 0) {
|
||||||
const uniqueDefs = [...new Set(defs)];
|
wordInfoV = getWordAt(row + 1, col, 'horizontal');
|
||||||
|
} else {
|
||||||
currentModal = { row, col, half, direction: targetDir };
|
wordInfoV = getWordAt(row + 1, col, 'vertical');
|
||||||
showDefinitions(targetWord.word, uniqueDefs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build words and definitions for both sides (may be empty)
|
||||||
|
const wordH = (wordInfoH && wordInfoH.word) ? wordInfoH.word : null;
|
||||||
|
const wordV = (wordInfoV && wordInfoV.word) ? wordInfoV.word : null;
|
||||||
|
|
||||||
|
const defsH = wordH ? ([...new Set(dictionaries[normalizeWord(wordH)] || [])]) : [];
|
||||||
|
const defsV = wordV ? ([...new Set(dictionaries[normalizeWord(wordV)] || [])]) : [];
|
||||||
|
|
||||||
|
// If half specified, still show modal but prioritize that half for selection via UI
|
||||||
|
currentModal = { row, col, half };
|
||||||
|
showDefinitionModal(row, col, wordH, defsH, wordV, defsV);
|
||||||
} else {
|
} else {
|
||||||
if (activeCell && activeCell.row === row && activeCell.col === col) {
|
if (activeCell && activeCell.row === row && activeCell.col === col) {
|
||||||
orientation = orientation === 'horizontal' ? 'vertical' : 'horizontal';
|
orientation = orientation === 'horizontal' ? 'vertical' : 'horizontal';
|
||||||
@ -527,44 +526,110 @@
|
|||||||
renderGrid();
|
renderGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Affiche les définitions
|
// Affiche le modal de sélection des définitions, en deux parties (horizontal / vertical)
|
||||||
function showDefinitions(word, definitions) {
|
function showDefinitionModal(row, col, wordH, defsH, wordV, defsV) {
|
||||||
document.getElementById('modalTitle').textContent = `Définitions pour "${word}"`;
|
currentModal = { row, col };
|
||||||
|
document.getElementById('modalTitle').textContent = `Définitions`;
|
||||||
const list = document.getElementById('definitionList');
|
const list = document.getElementById('definitionList');
|
||||||
list.innerHTML = '';
|
list.innerHTML = '';
|
||||||
|
|
||||||
if (definitions.length === 0) {
|
const sectionH = document.createElement('div');
|
||||||
list.innerHTML = '<p style="color: #6b7280;">Aucune définition disponible pour ce mot.</p>';
|
const titleH = document.createElement('h4');
|
||||||
|
titleH.textContent = 'Mot horizontal';
|
||||||
|
sectionH.appendChild(titleH);
|
||||||
|
if (!wordH) {
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.style.color = '#6b7280';
|
||||||
|
p.textContent = 'Aucun mot associé';
|
||||||
|
sectionH.appendChild(p);
|
||||||
} else {
|
} else {
|
||||||
definitions.forEach(def => {
|
const wordP = document.createElement('p');
|
||||||
const btn = document.createElement('button');
|
wordP.style.fontWeight = '600';
|
||||||
btn.className = 'definition-item';
|
wordP.textContent = wordH;
|
||||||
btn.textContent = def;
|
sectionH.appendChild(wordP);
|
||||||
btn.onclick = () => selectDefinition(def);
|
|
||||||
list.appendChild(btn);
|
if (defsH.length === 0) {
|
||||||
});
|
const p = document.createElement('p');
|
||||||
|
p.style.color = '#6b7280';
|
||||||
|
p.textContent = 'Aucune définition disponible pour ce mot.';
|
||||||
|
sectionH.appendChild(p);
|
||||||
|
} else {
|
||||||
|
defsH.forEach(def => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'definition-item';
|
||||||
|
btn.textContent = def;
|
||||||
|
btn.onclick = () => {
|
||||||
|
grid[row][col].definitionH = def;
|
||||||
|
closeModal();
|
||||||
|
renderGrid();
|
||||||
|
};
|
||||||
|
sectionH.appendChild(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sectionV = document.createElement('div');
|
||||||
|
const titleV = document.createElement('h4');
|
||||||
|
titleV.textContent = 'Mot vertical';
|
||||||
|
sectionV.appendChild(titleV);
|
||||||
|
if (!wordV) {
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.style.color = '#6b7280';
|
||||||
|
p.textContent = 'Aucun mot associé';
|
||||||
|
sectionV.appendChild(p);
|
||||||
|
} else {
|
||||||
|
const wordP = document.createElement('p');
|
||||||
|
wordP.style.fontWeight = '600';
|
||||||
|
wordP.textContent = wordV;
|
||||||
|
sectionV.appendChild(wordP);
|
||||||
|
|
||||||
|
if (defsV.length === 0) {
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.style.color = '#6b7280';
|
||||||
|
p.textContent = 'Aucune définition disponible pour ce mot.';
|
||||||
|
sectionV.appendChild(p);
|
||||||
|
} else {
|
||||||
|
defsV.forEach(def => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'definition-item';
|
||||||
|
btn.textContent = def;
|
||||||
|
btn.onclick = () => {
|
||||||
|
grid[row][col].definitionV = def;
|
||||||
|
closeModal();
|
||||||
|
renderGrid();
|
||||||
|
};
|
||||||
|
sectionV.appendChild(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete button: transforms case into letter empty
|
||||||
|
const deleteBtn = document.createElement('button');
|
||||||
|
deleteBtn.className = 'btn-danger';
|
||||||
|
deleteBtn.style.marginTop = '12px';
|
||||||
|
deleteBtn.textContent = 'Supprimer (transformer en case lettre)';
|
||||||
|
deleteBtn.onclick = () => {
|
||||||
|
const cell = grid[row][col];
|
||||||
|
const hasDefs = !!(cell.definitionH || cell.definitionV);
|
||||||
|
if (hasDefs) {
|
||||||
|
if (!confirm('La case contient des définitions. Confirmer la transformation en case lettre vide ?')) return;
|
||||||
|
}
|
||||||
|
cell.type = 'letter';
|
||||||
|
cell.letter = '';
|
||||||
|
cell.definitionH = '';
|
||||||
|
cell.definitionV = '';
|
||||||
|
closeModal();
|
||||||
|
renderGrid();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Append sections to list
|
||||||
|
list.appendChild(sectionH);
|
||||||
|
list.appendChild(sectionV);
|
||||||
|
list.appendChild(deleteBtn);
|
||||||
|
|
||||||
document.getElementById('definitionModal').classList.add('show');
|
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() {
|
function closeModal() {
|
||||||
document.getElementById('definitionModal').classList.remove('show');
|
document.getElementById('definitionModal').classList.remove('show');
|
||||||
currentModal = null;
|
currentModal = null;
|
||||||
@ -763,47 +828,36 @@
|
|||||||
} else {
|
} else {
|
||||||
hasBoth = cell.definitionH && cell.definitionV;
|
hasBoth = cell.definitionH && cell.definitionV;
|
||||||
|
|
||||||
if (hasBoth) {
|
if (hasBoth) {
|
||||||
const split = document.createElement('div');
|
const split = document.createElement('div');
|
||||||
split.className = 'definition-split';
|
split.className = 'definition-split';
|
||||||
|
|
||||||
const halfH = document.createElement('div');
|
const halfH = document.createElement('div');
|
||||||
halfH.className = 'definition-half';
|
halfH.className = 'definition-half';
|
||||||
halfH.textContent = cell.definitionH;
|
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');
|
const halfV = document.createElement('div');
|
||||||
halfV.className = 'definition-half';
|
halfV.className = 'definition-half';
|
||||||
halfV.textContent = cell.definitionV;
|
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(halfH);
|
||||||
split.appendChild(halfV);
|
split.appendChild(halfV);
|
||||||
cellEl.appendChild(split);
|
cellEl.appendChild(split);
|
||||||
|
|
||||||
halfH.onclick = (e) => {
|
halfH.onclick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleCellClick(rowIndex, colIndex, 'h');
|
handleCellClick(rowIndex, colIndex, 'h');
|
||||||
};
|
};
|
||||||
halfV.onclick = (e) => {
|
halfV.onclick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleCellClick(rowIndex, colIndex, 'v');
|
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 {
|
} 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>';
|
const text = document.createElement('div');
|
||||||
|
text.className = 'definition-text';
|
||||||
|
text.textContent = cell.definitionH || cell.definitionV;
|
||||||
|
cellEl.appendChild(text);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cellEl.appendChild(text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasBoth) {
|
if (!hasBoth) {
|
||||||
@ -812,6 +866,10 @@
|
|||||||
cellEl.ondblclick = () => handleCellDoubleClick(rowIndex, colIndex);
|
cellEl.ondblclick = () => handleCellDoubleClick(rowIndex, colIndex);
|
||||||
|
|
||||||
rowEl.appendChild(cellEl);
|
rowEl.appendChild(cellEl);
|
||||||
|
// store reference for later arrow placement
|
||||||
|
if (!window._cellEls) window._cellEls = [];
|
||||||
|
if (!window._cellEls[rowIndex]) window._cellEls[rowIndex] = [];
|
||||||
|
window._cellEls[rowIndex][colIndex] = cellEl;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bouton d'ajout de colonne
|
// Bouton d'ajout de colonne
|
||||||
@ -833,6 +891,90 @@
|
|||||||
addRowLine.appendChild(addBtn);
|
addRowLine.appendChild(addBtn);
|
||||||
}
|
}
|
||||||
gridEl.appendChild(addRowLine);
|
gridEl.appendChild(addRowLine);
|
||||||
|
|
||||||
|
// After grid built, place arrows in the first-letter cells based on adjacent definition cells
|
||||||
|
// Clear any previous arrows
|
||||||
|
if (window._cellEls) {
|
||||||
|
for (let r = 0; r < grid.length; r++) {
|
||||||
|
for (let c = 0; c < grid[0].length; c++) {
|
||||||
|
const el = window._cellEls[r] && window._cellEls[r][c];
|
||||||
|
if (el) {
|
||||||
|
const prev = el.querySelector('.def-arrow');
|
||||||
|
if (prev) prev.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each definition cell, compute target first-letter cell and append arrow there
|
||||||
|
for (let dr = 0; dr < grid.length; dr++) {
|
||||||
|
for (let dc = 0; dc < grid[0].length; dc++) {
|
||||||
|
const defCell = grid[dr][dc];
|
||||||
|
if (defCell.type !== 'definition') continue;
|
||||||
|
|
||||||
|
// Horizontal-definition mapping
|
||||||
|
if (defCell.definitionH) {
|
||||||
|
const tr = dr;
|
||||||
|
const tc = dc + 1;
|
||||||
|
if (tr >= 0 && tr < grid.length && tc >= 0 && tc < grid[0].length) {
|
||||||
|
const targetEl = window._cellEls[tr] && window._cellEls[tr][tc];
|
||||||
|
if (targetEl) {
|
||||||
|
const arrow = document.createElement('div');
|
||||||
|
arrow.className = 'def-arrow';
|
||||||
|
// special top-row: curve down
|
||||||
|
if (dr === 0) {
|
||||||
|
arrow.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1" class="arrow-down-curved"><path d="M12 0 C12 0 12 6 12 6 L12 16"/><path d="M12 16 L9 13 M12 16 L15 13"/></svg>';
|
||||||
|
arrow.style.position = 'absolute';
|
||||||
|
arrow.style.top = '2px';
|
||||||
|
arrow.style.left = '50%';
|
||||||
|
arrow.style.transform = 'translateX(-50%)';
|
||||||
|
} else {
|
||||||
|
// left-pointing arrow near left border
|
||||||
|
arrow.innerHTML = '<svg width="12" height="12" viewBox="0 0 8 8" fill="none" stroke="black" stroke-width="1"><path d="M6 1 L2 4 L6 7"/></svg>';
|
||||||
|
arrow.style.position = 'absolute';
|
||||||
|
arrow.style.left = '4px';
|
||||||
|
arrow.style.top = '50%';
|
||||||
|
arrow.style.transform = 'translateY(-50%)';
|
||||||
|
}
|
||||||
|
arrow.style.pointerEvents = 'none';
|
||||||
|
arrow.classList.add('def-arrow');
|
||||||
|
targetEl.appendChild(arrow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical-definition mapping
|
||||||
|
if (defCell.definitionV) {
|
||||||
|
const tr = dr + 1;
|
||||||
|
const tc = dc;
|
||||||
|
if (tr >= 0 && tr < grid.length && tc >= 0 && tc < grid[0].length) {
|
||||||
|
const targetEl = window._cellEls[tr] && window._cellEls[tr][tc];
|
||||||
|
if (targetEl) {
|
||||||
|
const arrow = document.createElement('div');
|
||||||
|
arrow.className = 'def-arrow';
|
||||||
|
if (dc === 0) {
|
||||||
|
// left-edge special: curve right
|
||||||
|
arrow.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1"><path d="M0 12 C0 12 6 12 6 12 L16 12"/><path d="M16 12 L13 9 M16 12 L13 15"/></svg>';
|
||||||
|
arrow.style.position = 'absolute';
|
||||||
|
arrow.style.left = '2px';
|
||||||
|
arrow.style.top = '50%';
|
||||||
|
arrow.style.transform = 'translateY(-50%)';
|
||||||
|
} else {
|
||||||
|
// up-pointing arrow near top border
|
||||||
|
arrow.innerHTML = '<svg width="12" height="12" viewBox="0 0 8 8" fill="none" stroke="black" stroke-width="1"><path d="M1 6 L4 2 L7 6"/></svg>';
|
||||||
|
arrow.style.position = 'absolute';
|
||||||
|
arrow.style.top = '4px';
|
||||||
|
arrow.style.left = '50%';
|
||||||
|
arrow.style.transform = 'translateX(-50%)';
|
||||||
|
}
|
||||||
|
arrow.style.pointerEvents = 'none';
|
||||||
|
arrow.classList.add('def-arrow');
|
||||||
|
targetEl.appendChild(arrow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestion du clavier
|
// Gestion du clavier
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user