living-ai-documentation 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.fr.md +344 -0
- package/README.md +344 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +262 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/frontend/accuracy-gauge.js +70 -0
- package/dist/src/frontend/admin.html +1532 -0
- package/dist/src/frontend/annotations.js +585 -0
- package/dist/src/frontend/boot.js +101 -0
- package/dist/src/frontend/config.js +29 -0
- package/dist/src/frontend/confirm-modal.js +82 -0
- package/dist/src/frontend/context.html +1252 -0
- package/dist/src/frontend/dark-mode.js +20 -0
- package/dist/src/frontend/diagram/alignment.js +161 -0
- package/dist/src/frontend/diagram/clipboard.js +187 -0
- package/dist/src/frontend/diagram/constants.js +109 -0
- package/dist/src/frontend/diagram/custom-shapes.js +104 -0
- package/dist/src/frontend/diagram/debug.js +43 -0
- package/dist/src/frontend/diagram/drawio-export.js +649 -0
- package/dist/src/frontend/diagram/edge-panel.js +293 -0
- package/dist/src/frontend/diagram/edge-rendering.js +12 -0
- package/dist/src/frontend/diagram/evidence.js +146 -0
- package/dist/src/frontend/diagram/grid.js +78 -0
- package/dist/src/frontend/diagram/groups.js +102 -0
- package/dist/src/frontend/diagram/history.js +157 -0
- package/dist/src/frontend/diagram/image-name-modal.js +48 -0
- package/dist/src/frontend/diagram/image-upload.js +36 -0
- package/dist/src/frontend/diagram/label-editor.js +115 -0
- package/dist/src/frontend/diagram/link-panel.js +144 -0
- package/dist/src/frontend/diagram/main.js +364 -0
- package/dist/src/frontend/diagram/network.js +2214 -0
- package/dist/src/frontend/diagram/node-panel.js +389 -0
- package/dist/src/frontend/diagram/node-rendering.js +964 -0
- package/dist/src/frontend/diagram/persistence.js +168 -0
- package/dist/src/frontend/diagram/ports.js +421 -0
- package/dist/src/frontend/diagram/selection-overlay.js +387 -0
- package/dist/src/frontend/diagram/state.js +43 -0
- package/dist/src/frontend/diagram/t.js +3 -0
- package/dist/src/frontend/diagram/toast.js +21 -0
- package/dist/src/frontend/diagram/unlock-hold.js +206 -0
- package/dist/src/frontend/diagram/zoom.js +20 -0
- package/dist/src/frontend/diagram-link-modal.js +137 -0
- package/dist/src/frontend/diagram.html +1494 -0
- package/dist/src/frontend/documents.js +479 -0
- package/dist/src/frontend/export.js +338 -0
- package/dist/src/frontend/file-attach.js +178 -0
- package/dist/src/frontend/files-modal.js +243 -0
- package/dist/src/frontend/i18n/en.json +624 -0
- package/dist/src/frontend/i18n/fr.json +624 -0
- package/dist/src/frontend/i18n.js +32 -0
- package/dist/src/frontend/image-paste.js +126 -0
- package/dist/src/frontend/index.html +2806 -0
- package/dist/src/frontend/local-search.js +476 -0
- package/dist/src/frontend/metadata.js +318 -0
- package/dist/src/frontend/misc.js +92 -0
- package/dist/src/frontend/new-doc-modal.js +285 -0
- package/dist/src/frontend/new-folder-modal.js +169 -0
- package/dist/src/frontend/search.js +194 -0
- package/dist/src/frontend/shape-editor.html +685 -0
- package/dist/src/frontend/sidebar-helpers.js +96 -0
- package/dist/src/frontend/sidebar-resize.js +98 -0
- package/dist/src/frontend/sidebar.js +351 -0
- package/dist/src/frontend/snippet-detect.js +25 -0
- package/dist/src/frontend/snippet-table.js +85 -0
- package/dist/src/frontend/snippet-tree.js +94 -0
- package/dist/src/frontend/snippets.js +1146 -0
- package/dist/src/frontend/state.js +46 -0
- package/dist/src/frontend/utils.js +21 -0
- package/dist/src/frontend/validate.js +107 -0
- package/dist/src/frontend/vendor/wordcloud2.js +1187 -0
- package/dist/src/frontend/wordcloud.js +693 -0
- package/dist/src/lib/config.d.ts +26 -0
- package/dist/src/lib/config.d.ts.map +1 -0
- package/dist/src/lib/config.js +195 -0
- package/dist/src/lib/config.js.map +1 -0
- package/dist/src/lib/hash.d.ts +2 -0
- package/dist/src/lib/hash.d.ts.map +1 -0
- package/dist/src/lib/hash.js +18 -0
- package/dist/src/lib/hash.js.map +1 -0
- package/dist/src/lib/metadata.d.ts +31 -0
- package/dist/src/lib/metadata.d.ts.map +1 -0
- package/dist/src/lib/metadata.js +128 -0
- package/dist/src/lib/metadata.js.map +1 -0
- package/dist/src/lib/parser.d.ts +11 -0
- package/dist/src/lib/parser.d.ts.map +1 -0
- package/dist/src/lib/parser.js +111 -0
- package/dist/src/lib/parser.js.map +1 -0
- package/dist/src/lib/status.d.ts +9 -0
- package/dist/src/lib/status.d.ts.map +1 -0
- package/dist/src/lib/status.js +72 -0
- package/dist/src/lib/status.js.map +1 -0
- package/dist/src/mcp/server.d.ts +3 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +2046 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools/diagrams.d.ts +82 -0
- package/dist/src/mcp/tools/diagrams.d.ts.map +1 -0
- package/dist/src/mcp/tools/diagrams.js +594 -0
- package/dist/src/mcp/tools/diagrams.js.map +1 -0
- package/dist/src/mcp/tools/documents.d.ts +44 -0
- package/dist/src/mcp/tools/documents.d.ts.map +1 -0
- package/dist/src/mcp/tools/documents.js +186 -0
- package/dist/src/mcp/tools/documents.js.map +1 -0
- package/dist/src/mcp/tools/git.d.ts +10 -0
- package/dist/src/mcp/tools/git.d.ts.map +1 -0
- package/dist/src/mcp/tools/git.js +217 -0
- package/dist/src/mcp/tools/git.js.map +1 -0
- package/dist/src/mcp/tools/metadata.d.ts +57 -0
- package/dist/src/mcp/tools/metadata.d.ts.map +1 -0
- package/dist/src/mcp/tools/metadata.js +222 -0
- package/dist/src/mcp/tools/metadata.js.map +1 -0
- package/dist/src/mcp/tools/source.d.ts +29 -0
- package/dist/src/mcp/tools/source.d.ts.map +1 -0
- package/dist/src/mcp/tools/source.js +196 -0
- package/dist/src/mcp/tools/source.js.map +1 -0
- package/dist/src/routes/annotations.d.ts +3 -0
- package/dist/src/routes/annotations.d.ts.map +1 -0
- package/dist/src/routes/annotations.js +83 -0
- package/dist/src/routes/annotations.js.map +1 -0
- package/dist/src/routes/browse-source.d.ts +3 -0
- package/dist/src/routes/browse-source.d.ts.map +1 -0
- package/dist/src/routes/browse-source.js +79 -0
- package/dist/src/routes/browse-source.js.map +1 -0
- package/dist/src/routes/browse.d.ts +3 -0
- package/dist/src/routes/browse.d.ts.map +1 -0
- package/dist/src/routes/browse.js +91 -0
- package/dist/src/routes/browse.js.map +1 -0
- package/dist/src/routes/config.d.ts +3 -0
- package/dist/src/routes/config.d.ts.map +1 -0
- package/dist/src/routes/config.js +145 -0
- package/dist/src/routes/config.js.map +1 -0
- package/dist/src/routes/context.d.ts +3 -0
- package/dist/src/routes/context.d.ts.map +1 -0
- package/dist/src/routes/context.js +287 -0
- package/dist/src/routes/context.js.map +1 -0
- package/dist/src/routes/diagrams.d.ts +3 -0
- package/dist/src/routes/diagrams.d.ts.map +1 -0
- package/dist/src/routes/diagrams.js +69 -0
- package/dist/src/routes/diagrams.js.map +1 -0
- package/dist/src/routes/documents.d.ts +11 -0
- package/dist/src/routes/documents.d.ts.map +1 -0
- package/dist/src/routes/documents.js +450 -0
- package/dist/src/routes/documents.js.map +1 -0
- package/dist/src/routes/export.d.ts +3 -0
- package/dist/src/routes/export.d.ts.map +1 -0
- package/dist/src/routes/export.js +280 -0
- package/dist/src/routes/export.js.map +1 -0
- package/dist/src/routes/files.d.ts +3 -0
- package/dist/src/routes/files.d.ts.map +1 -0
- package/dist/src/routes/files.js +180 -0
- package/dist/src/routes/files.js.map +1 -0
- package/dist/src/routes/images.d.ts +3 -0
- package/dist/src/routes/images.d.ts.map +1 -0
- package/dist/src/routes/images.js +49 -0
- package/dist/src/routes/images.js.map +1 -0
- package/dist/src/routes/metadata.d.ts +3 -0
- package/dist/src/routes/metadata.d.ts.map +1 -0
- package/dist/src/routes/metadata.js +131 -0
- package/dist/src/routes/metadata.js.map +1 -0
- package/dist/src/routes/shape-libraries.d.ts +3 -0
- package/dist/src/routes/shape-libraries.d.ts.map +1 -0
- package/dist/src/routes/shape-libraries.js +118 -0
- package/dist/src/routes/shape-libraries.js.map +1 -0
- package/dist/src/routes/wordcloud.d.ts +3 -0
- package/dist/src/routes/wordcloud.d.ts.map +1 -0
- package/dist/src/routes/wordcloud.js +95 -0
- package/dist/src/routes/wordcloud.js.map +1 -0
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +93 -0
- package/dist/src/server.js.map +1 -0
- package/dist/starter-doc/.living-doc.json +52 -0
- package/dist/starter-doc/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
- package/dist/starter-doc/AI/2026_01_01_how_to.md +112 -0
- package/dist/starter-doc/AI/PROJECT-INSTRUCTIONS.md +172 -0
- package/dist/starter-doc/AI/PROJECT-STACK.md +77 -0
- package/dist/starter-doc/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
- package/dist/starter-doc/AI/default/AGENTS.md +31 -0
- package/dist/starter-doc/AI/default/CLAUDE.md +31 -0
- package/dist/starter-doc/AI/default/MEMORY.md +24 -0
- package/dist/starter-doc/AI/rules/no-magic-numbers.md +18 -0
- package/dist/starter-doc/AI/rules/track-current-work.md +23 -0
- package/dist/starter-doc/WORKLOG/current-task.md +57 -0
- package/dist/starter-doc-fr/.living-doc.json +52 -0
- package/dist/starter-doc-fr/ADRS/2026_01_01_[ADR]_example_architecture_decision.md +59 -0
- package/dist/starter-doc-fr/AI/2026_01_01_how_to.md +100 -0
- package/dist/starter-doc-fr/AI/PROJECT-INSTRUCTIONS.md +172 -0
- package/dist/starter-doc-fr/AI/PROJECT-STACK.md +77 -0
- package/dist/starter-doc-fr/AI/PROJECT-USEFUL-COMMANDS.md +80 -0
- package/dist/starter-doc-fr/AI/default/AGENTS.md +31 -0
- package/dist/starter-doc-fr/AI/default/CLAUDE.md +31 -0
- package/dist/starter-doc-fr/AI/default/MEMORY.md +24 -0
- package/dist/starter-doc-fr/AI/rules/no-magic-numbers.md +18 -0
- package/dist/starter-doc-fr/AI/rules/track-current-work.md +23 -0
- package/dist/starter-doc-fr/WORKLOG/current-task.md +57 -0
- package/images/living_documentation.jpg +0 -0
- package/images/readme-extra-files.png +0 -0
- package/images/readme-filename-pattern.png +0 -0
- package/images/readme-intelligent-search-demo.jpg +0 -0
- package/images/readme-sidebar.png +0 -0
- package/package.json +72 -0
|
@@ -0,0 +1,1146 @@
|
|
|
1
|
+
// ── Snippets modal: type switching, preview, insert, parse ──────────────────
|
|
2
|
+
// Depends on globals from state.js (allDocs, currentDocId, currentDocContent),
|
|
3
|
+
// utils.js (esc), snippet-detect.js (detectSnippetType), snippet-table.js
|
|
4
|
+
// (_tableData, tableInit, tableRenderGrid, buildTableMarkdown) and
|
|
5
|
+
// snippet-tree.js (_treeItems, treeInit, treeRenderList, buildTreeMarkdown).
|
|
6
|
+
|
|
7
|
+
let _snippetSelStart = 0;
|
|
8
|
+
let _snippetSelEnd = 0;
|
|
9
|
+
const _SNIPPET_PANELS = [
|
|
10
|
+
"collapsible",
|
|
11
|
+
"link",
|
|
12
|
+
"doc-link",
|
|
13
|
+
"anchor-link",
|
|
14
|
+
"anchor-doc-link",
|
|
15
|
+
"code-block",
|
|
16
|
+
"image",
|
|
17
|
+
"table",
|
|
18
|
+
"tree",
|
|
19
|
+
"diagram",
|
|
20
|
+
"colored-section",
|
|
21
|
+
"colored-text",
|
|
22
|
+
"emojis",
|
|
23
|
+
"attachment",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Each emoji has search tags (bilingual FR/EN, space-separated, lowercase).
|
|
27
|
+
// Filter matches a 2+ char query against any tag prefix or substring.
|
|
28
|
+
const _EMOJI_CATEGORIES = [
|
|
29
|
+
{ label: 'snippet.emoji_cat_smileys', emojis: [
|
|
30
|
+
{ e: '😀', t: 'grin grinning smile sourire happy heureux' },
|
|
31
|
+
{ e: '😃', t: 'smiley joy joie sourire happy' },
|
|
32
|
+
{ e: '😄', t: 'smile sourire happy heureux joie' },
|
|
33
|
+
{ e: '😁', t: 'beaming grin sourire dents' },
|
|
34
|
+
{ e: '😆', t: 'laugh rire lol' },
|
|
35
|
+
{ e: '😅', t: 'sweat transpire rire nerveux' },
|
|
36
|
+
{ e: '🤣', t: 'rofl lol mdr rire' },
|
|
37
|
+
{ e: '😂', t: 'joy larmes tears rire lol' },
|
|
38
|
+
{ e: '🙂', t: 'slight smile sourire' },
|
|
39
|
+
{ e: '🙃', t: 'upside renverse inverse' },
|
|
40
|
+
{ e: '😉', t: 'wink clin oeil' },
|
|
41
|
+
{ e: '😊', t: 'blush rougir sourire' },
|
|
42
|
+
{ e: '😇', t: 'angel ange halo innocent' },
|
|
43
|
+
{ e: '🥰', t: 'love amour coeur heart smile' },
|
|
44
|
+
{ e: '😍', t: 'heart eyes amoureux love amour' },
|
|
45
|
+
{ e: '🤩', t: 'star stars etoile wow' },
|
|
46
|
+
{ e: '😘', t: 'kiss bisou' },
|
|
47
|
+
{ e: '😋', t: 'yum miam savoureux langue' },
|
|
48
|
+
{ e: '😛', t: 'tongue langue' },
|
|
49
|
+
{ e: '😜', t: 'wink tongue langue clin' },
|
|
50
|
+
{ e: '🤪', t: 'crazy fou zany' },
|
|
51
|
+
{ e: '😝', t: 'tongue langue squinting' },
|
|
52
|
+
{ e: '🤗', t: 'hug calin embrasser' },
|
|
53
|
+
{ e: '🤔', t: 'think reflexion reflechir pense' },
|
|
54
|
+
{ e: '🤨', t: 'raised brow sourcil suspicious' },
|
|
55
|
+
{ e: '😐', t: 'neutral neutre' },
|
|
56
|
+
{ e: '😑', t: 'expressionless impassible' },
|
|
57
|
+
{ e: '😒', t: 'unamused decu blase' },
|
|
58
|
+
{ e: '🙄', t: 'roll eyes yeux ciel' },
|
|
59
|
+
{ e: '😬', t: 'grimace' },
|
|
60
|
+
{ e: '😌', t: 'relieved soulage' },
|
|
61
|
+
{ e: '😔', t: 'pensive pensif triste sad' },
|
|
62
|
+
{ e: '😪', t: 'sleepy sommeil fatigue' },
|
|
63
|
+
{ e: '😴', t: 'sleep dormir zzz' },
|
|
64
|
+
{ e: '🤤', t: 'drool bave' },
|
|
65
|
+
{ e: '😷', t: 'mask masque malade' },
|
|
66
|
+
{ e: '🤒', t: 'sick malade fever fievre' },
|
|
67
|
+
{ e: '🤕', t: 'hurt blesse bandage' },
|
|
68
|
+
{ e: '🤢', t: 'nausea nausee' },
|
|
69
|
+
{ e: '🤮', t: 'vomit vomir malade' },
|
|
70
|
+
{ e: '🤧', t: 'sneeze eternue rhume' },
|
|
71
|
+
{ e: '🥵', t: 'hot chaud transpire' },
|
|
72
|
+
{ e: '🥶', t: 'cold froid gele' },
|
|
73
|
+
{ e: '🥴', t: 'woozy ivre' },
|
|
74
|
+
{ e: '😵', t: 'dizzy dead mort vertige' },
|
|
75
|
+
{ e: '🤯', t: 'explode tete mind blown' },
|
|
76
|
+
{ e: '🤠', t: 'cowboy chapeau' },
|
|
77
|
+
{ e: '🥳', t: 'party fete anniversaire birthday' },
|
|
78
|
+
{ e: '😎', t: 'cool sunglasses lunettes' },
|
|
79
|
+
{ e: '🤓', t: 'nerd geek' },
|
|
80
|
+
{ e: '🧐', t: 'monocle curious' },
|
|
81
|
+
{ e: '😕', t: 'confused confus' },
|
|
82
|
+
{ e: '😟', t: 'worried inquiet' },
|
|
83
|
+
{ e: '🙁', t: 'frown triste' },
|
|
84
|
+
{ e: '☹️', t: 'sad triste frown' },
|
|
85
|
+
{ e: '😮', t: 'surprised surpris open mouth' },
|
|
86
|
+
{ e: '😯', t: 'hushed' },
|
|
87
|
+
{ e: '😲', t: 'astonished etonne' },
|
|
88
|
+
{ e: '😳', t: 'flushed gene rougeur' },
|
|
89
|
+
{ e: '🥺', t: 'pleading supplier chien' },
|
|
90
|
+
{ e: '😦', t: 'frown open mouth' },
|
|
91
|
+
{ e: '😧', t: 'anguish angoisse' },
|
|
92
|
+
{ e: '😨', t: 'fear peur' },
|
|
93
|
+
{ e: '😰', t: 'anxious anxieux stress' },
|
|
94
|
+
{ e: '😥', t: 'sad disappointed decu' },
|
|
95
|
+
{ e: '😢', t: 'cry pleurer larme' },
|
|
96
|
+
{ e: '😭', t: 'cry pleurer loud bawling' },
|
|
97
|
+
{ e: '😱', t: 'scream cri peur horror' },
|
|
98
|
+
{ e: '😖', t: 'confounded' },
|
|
99
|
+
{ e: '😣', t: 'persevere effort' },
|
|
100
|
+
{ e: '😞', t: 'disappointed decu' },
|
|
101
|
+
{ e: '😓', t: 'sweat transpire fatigue' },
|
|
102
|
+
{ e: '😩', t: 'weary fatigue las' },
|
|
103
|
+
{ e: '😫', t: 'tired fatigue' },
|
|
104
|
+
{ e: '🥱', t: 'yawn baille' },
|
|
105
|
+
{ e: '😤', t: 'triumph huff vapeur nez' },
|
|
106
|
+
{ e: '😠', t: 'angry colere fache' },
|
|
107
|
+
{ e: '😡', t: 'rage colere rouge' },
|
|
108
|
+
{ e: '🤬', t: 'curse insulte jure' },
|
|
109
|
+
{ e: '😈', t: 'devil diable smiling' },
|
|
110
|
+
{ e: '👿', t: 'imp diable' },
|
|
111
|
+
{ e: '💀', t: 'skull mort death crane' },
|
|
112
|
+
{ e: '☠️', t: 'crossbones pirate mort death' },
|
|
113
|
+
{ e: '💩', t: 'poop caca merde' },
|
|
114
|
+
{ e: '🤡', t: 'clown' },
|
|
115
|
+
{ e: '👻', t: 'ghost fantome' },
|
|
116
|
+
{ e: '👽', t: 'alien extraterrestre' },
|
|
117
|
+
{ e: '🤖', t: 'robot ia ai' },
|
|
118
|
+
{ e: '🎃', t: 'pumpkin halloween citrouille' },
|
|
119
|
+
{ e: '😺', t: 'cat chat' },
|
|
120
|
+
{ e: '😸', t: 'cat chat grin' },
|
|
121
|
+
{ e: '😹', t: 'cat chat joy' },
|
|
122
|
+
{ e: '😻', t: 'cat chat love amour heart' },
|
|
123
|
+
{ e: '😼', t: 'cat chat wry' },
|
|
124
|
+
{ e: '😽', t: 'cat chat kiss' },
|
|
125
|
+
{ e: '🙀', t: 'cat chat fear peur' },
|
|
126
|
+
{ e: '😿', t: 'cat chat cry pleurer' },
|
|
127
|
+
{ e: '😾', t: 'cat chat angry colere' },
|
|
128
|
+
]},
|
|
129
|
+
{ label: 'snippet.emoji_cat_gestures', emojis: [
|
|
130
|
+
{ e: '👍', t: 'thumbs up pouce ok bien like' },
|
|
131
|
+
{ e: '👎', t: 'thumbs down pouce bas dislike' },
|
|
132
|
+
{ e: '👌', t: 'ok parfait' },
|
|
133
|
+
{ e: '✌️', t: 'victory victoire peace paix' },
|
|
134
|
+
{ e: '🤞', t: 'fingers crossed doigts croises' },
|
|
135
|
+
{ e: '🤟', t: 'love you' },
|
|
136
|
+
{ e: '🤘', t: 'rock horns metal cornes' },
|
|
137
|
+
{ e: '🤙', t: 'call appelle hang loose' },
|
|
138
|
+
{ e: '👈', t: 'left gauche pointing' },
|
|
139
|
+
{ e: '👉', t: 'right droite pointing' },
|
|
140
|
+
{ e: '👆', t: 'up haut pointing' },
|
|
141
|
+
{ e: '👇', t: 'down bas pointing' },
|
|
142
|
+
{ e: '☝️', t: 'one index up haut' },
|
|
143
|
+
{ e: '✋', t: 'stop hand main raised' },
|
|
144
|
+
{ e: '🤚', t: 'back hand main' },
|
|
145
|
+
{ e: '🖐️', t: 'hand main splayed' },
|
|
146
|
+
{ e: '🖖', t: 'vulcan spock star trek' },
|
|
147
|
+
{ e: '👋', t: 'wave bonjour hello salut goodbye' },
|
|
148
|
+
{ e: '🤝', t: 'handshake accord deal poignee' },
|
|
149
|
+
{ e: '🙏', t: 'pray priere merci thanks please' },
|
|
150
|
+
{ e: '💪', t: 'muscle fort biceps strong' },
|
|
151
|
+
{ e: '🦾', t: 'mechanical arm bras prothese' },
|
|
152
|
+
{ e: '👏', t: 'clap applaud bravo applaudir' },
|
|
153
|
+
{ e: '🙌', t: 'raise hands hourra celebrate' },
|
|
154
|
+
{ e: '👐', t: 'open hands' },
|
|
155
|
+
{ e: '🤲', t: 'palms up paumes' },
|
|
156
|
+
{ e: '🤜', t: 'fist poing right' },
|
|
157
|
+
{ e: '🤛', t: 'fist poing left' },
|
|
158
|
+
{ e: '✊', t: 'fist poing leve' },
|
|
159
|
+
{ e: '👊', t: 'fist punch poing frapper' },
|
|
160
|
+
{ e: '🤏', t: 'pinch petit small' },
|
|
161
|
+
{ e: '🤌', t: 'italian pinched' },
|
|
162
|
+
{ e: '🖕', t: 'middle finger doigt honneur' },
|
|
163
|
+
{ e: '✍️', t: 'write ecrire hand main' },
|
|
164
|
+
{ e: '🦶', t: 'foot pied' },
|
|
165
|
+
{ e: '🦵', t: 'leg jambe' },
|
|
166
|
+
{ e: '👂', t: 'ear oreille' },
|
|
167
|
+
{ e: '🦻', t: 'ear hearing appareil auditif' },
|
|
168
|
+
{ e: '👃', t: 'nose nez' },
|
|
169
|
+
{ e: '🧠', t: 'brain cerveau mind' },
|
|
170
|
+
{ e: '👀', t: 'eyes yeux regarder' },
|
|
171
|
+
{ e: '👁️', t: 'eye oeil' },
|
|
172
|
+
{ e: '👄', t: 'mouth bouche' },
|
|
173
|
+
{ e: '👅', t: 'tongue langue' },
|
|
174
|
+
{ e: '🦷', t: 'tooth dent' },
|
|
175
|
+
{ e: '🫀', t: 'heart anatomic coeur organe' },
|
|
176
|
+
{ e: '🫁', t: 'lungs poumons' },
|
|
177
|
+
]},
|
|
178
|
+
{ label: 'snippet.emoji_cat_hearts', emojis: [
|
|
179
|
+
{ e: '❤️', t: 'heart coeur red rouge love amour' },
|
|
180
|
+
{ e: '🧡', t: 'orange heart coeur' },
|
|
181
|
+
{ e: '💛', t: 'yellow heart coeur jaune' },
|
|
182
|
+
{ e: '💚', t: 'green heart coeur vert' },
|
|
183
|
+
{ e: '💙', t: 'blue heart coeur bleu' },
|
|
184
|
+
{ e: '💜', t: 'purple heart coeur violet' },
|
|
185
|
+
{ e: '🖤', t: 'black heart coeur noir' },
|
|
186
|
+
{ e: '🤍', t: 'white heart coeur blanc' },
|
|
187
|
+
{ e: '🤎', t: 'brown heart coeur marron' },
|
|
188
|
+
{ e: '💔', t: 'broken heart coeur brise rupture' },
|
|
189
|
+
{ e: '❤️🔥', t: 'heart fire feu passion' },
|
|
190
|
+
{ e: '❤️🩹', t: 'heart mending coeur reparation' },
|
|
191
|
+
{ e: '💖', t: 'sparkling heart coeur brillant' },
|
|
192
|
+
{ e: '💕', t: 'two hearts coeurs' },
|
|
193
|
+
{ e: '💘', t: 'arrow heart cupid cupidon' },
|
|
194
|
+
{ e: '💝', t: 'ribbon heart cadeau' },
|
|
195
|
+
{ e: '💞', t: 'revolving hearts coeurs' },
|
|
196
|
+
{ e: '💟', t: 'heart decoration' },
|
|
197
|
+
{ e: '💌', t: 'love letter lettre enveloppe' },
|
|
198
|
+
{ e: '💋', t: 'kiss mark bisou levres' },
|
|
199
|
+
{ e: '❣️', t: 'heart exclamation' },
|
|
200
|
+
{ e: '💯', t: 'hundred 100 cent perfect parfait' },
|
|
201
|
+
{ e: '✨', t: 'sparkles etincelles magic magique brillant' },
|
|
202
|
+
{ e: '⭐', t: 'star etoile' },
|
|
203
|
+
{ e: '🌟', t: 'glowing star etoile' },
|
|
204
|
+
{ e: '💫', t: 'dizzy star vertige etoile' },
|
|
205
|
+
{ e: '🔥', t: 'fire feu hot flamme' },
|
|
206
|
+
{ e: '💥', t: 'boom explosion collision' },
|
|
207
|
+
{ e: '⚡', t: 'lightning eclair thunder tonnerre fast' },
|
|
208
|
+
{ e: '💢', t: 'anger colere symbol' },
|
|
209
|
+
{ e: '💨', t: 'dash vent wind fast vitesse' },
|
|
210
|
+
{ e: '💦', t: 'drops gouttes sweat eau' },
|
|
211
|
+
{ e: '💤', t: 'zzz sleep dormir sommeil' },
|
|
212
|
+
{ e: '🕳️', t: 'hole trou' },
|
|
213
|
+
{ e: '💭', t: 'thought pensee bulle' },
|
|
214
|
+
{ e: '🗯️', t: 'right anger bubble' },
|
|
215
|
+
{ e: '💬', t: 'speech bulle dialogue chat' },
|
|
216
|
+
]},
|
|
217
|
+
{ label: 'snippet.emoji_cat_objects', emojis: [
|
|
218
|
+
{ e: '💻', t: 'laptop ordinateur computer portable' },
|
|
219
|
+
{ e: '🖥️', t: 'desktop computer ordinateur bureau' },
|
|
220
|
+
{ e: '⌨️', t: 'keyboard clavier' },
|
|
221
|
+
{ e: '🖱️', t: 'mouse souris' },
|
|
222
|
+
{ e: '💾', t: 'floppy disquette save sauvegarder' },
|
|
223
|
+
{ e: '💿', t: 'disk cd optical' },
|
|
224
|
+
{ e: '📀', t: 'dvd' },
|
|
225
|
+
{ e: '📱', t: 'phone mobile telephone smartphone' },
|
|
226
|
+
{ e: '📲', t: 'phone call mobile incoming' },
|
|
227
|
+
{ e: '☎️', t: 'telephone vintage' },
|
|
228
|
+
{ e: '📞', t: 'receiver combine phone' },
|
|
229
|
+
{ e: '📟', t: 'pager' },
|
|
230
|
+
{ e: '📠', t: 'fax' },
|
|
231
|
+
{ e: '🔋', t: 'battery batterie pile' },
|
|
232
|
+
{ e: '🔌', t: 'plug prise electrique' },
|
|
233
|
+
{ e: '💡', t: 'bulb idee idea light lumiere ampoule' },
|
|
234
|
+
{ e: '🔦', t: 'flashlight torche lampe' },
|
|
235
|
+
{ e: '🕯️', t: 'candle bougie' },
|
|
236
|
+
{ e: '📷', t: 'camera appareil photo' },
|
|
237
|
+
{ e: '📸', t: 'camera flash photo' },
|
|
238
|
+
{ e: '📹', t: 'video camera' },
|
|
239
|
+
{ e: '🎥', t: 'movie film cinema camera' },
|
|
240
|
+
{ e: '🎞️', t: 'film reel pellicule' },
|
|
241
|
+
{ e: '📽️', t: 'projector projecteur' },
|
|
242
|
+
{ e: '⏰', t: 'alarm reveil clock horloge' },
|
|
243
|
+
{ e: '⏱️', t: 'stopwatch chrono chronometre' },
|
|
244
|
+
{ e: '⏲️', t: 'timer minuteur' },
|
|
245
|
+
{ e: '🕰️', t: 'mantel clock horloge' },
|
|
246
|
+
{ e: '⌛', t: 'hourglass sablier done' },
|
|
247
|
+
{ e: '⏳', t: 'hourglass sablier flow running' },
|
|
248
|
+
{ e: '📡', t: 'satellite antenne' },
|
|
249
|
+
{ e: '🔭', t: 'telescope astronomie' },
|
|
250
|
+
{ e: '🔬', t: 'microscope science' },
|
|
251
|
+
{ e: '💊', t: 'pill pilule medicine medicament' },
|
|
252
|
+
{ e: '💉', t: 'syringe seringue injection vaccin' },
|
|
253
|
+
{ e: '🩹', t: 'bandage pansement' },
|
|
254
|
+
{ e: '🩺', t: 'stethoscope medecin' },
|
|
255
|
+
{ e: '🧪', t: 'tube eprouvette chimie chemistry' },
|
|
256
|
+
{ e: '🧬', t: 'dna adn gene' },
|
|
257
|
+
{ e: '🔍', t: 'search magnify loupe rechercher find left' },
|
|
258
|
+
{ e: '🔎', t: 'search magnify loupe rechercher find right' },
|
|
259
|
+
{ e: '🗝️', t: 'key cle old' },
|
|
260
|
+
{ e: '🔑', t: 'key cle' },
|
|
261
|
+
{ e: '🔐', t: 'lock key cle cadenas' },
|
|
262
|
+
{ e: '🔒', t: 'lock cadenas verrouille secure' },
|
|
263
|
+
{ e: '🔓', t: 'unlock cadenas ouvert' },
|
|
264
|
+
{ e: '🛡️', t: 'shield bouclier protection' },
|
|
265
|
+
{ e: '🔨', t: 'hammer marteau' },
|
|
266
|
+
{ e: '⚒️', t: 'tools outils hammer pick' },
|
|
267
|
+
{ e: '🛠️', t: 'tools outils hammer wrench' },
|
|
268
|
+
{ e: '🔧', t: 'wrench cle anglaise' },
|
|
269
|
+
{ e: '🔩', t: 'nut bolt ecrou' },
|
|
270
|
+
{ e: '⚙️', t: 'gear settings parametres engrenage config' },
|
|
271
|
+
{ e: '🪛', t: 'screwdriver tournevis' },
|
|
272
|
+
{ e: '🪝', t: 'hook crochet' },
|
|
273
|
+
{ e: '🧰', t: 'toolbox caisse outils' },
|
|
274
|
+
{ e: '🧲', t: 'magnet aimant' },
|
|
275
|
+
{ e: '🧱', t: 'brick brique' },
|
|
276
|
+
{ e: '⛏️', t: 'pick pioche' },
|
|
277
|
+
{ e: '🪓', t: 'axe hache' },
|
|
278
|
+
]},
|
|
279
|
+
{ label: 'snippet.emoji_cat_office', emojis: [
|
|
280
|
+
{ e: '📝', t: 'memo note ecrire write' },
|
|
281
|
+
{ e: '✏️', t: 'pencil crayon' },
|
|
282
|
+
{ e: '🖊️', t: 'pen stylo bille' },
|
|
283
|
+
{ e: '🖋️', t: 'fountain pen stylo plume' },
|
|
284
|
+
{ e: '🖌️', t: 'paintbrush pinceau' },
|
|
285
|
+
{ e: '🖍️', t: 'crayon feutre' },
|
|
286
|
+
{ e: '📐', t: 'triangle ruler equerre' },
|
|
287
|
+
{ e: '📏', t: 'ruler regle' },
|
|
288
|
+
{ e: '📎', t: 'paperclip trombone attach' },
|
|
289
|
+
{ e: '🖇️', t: 'linked paperclips trombones' },
|
|
290
|
+
{ e: '📌', t: 'pushpin punaise' },
|
|
291
|
+
{ e: '📍', t: 'pin location lieu' },
|
|
292
|
+
{ e: '✂️', t: 'scissors ciseaux cut couper' },
|
|
293
|
+
{ e: '🗂️', t: 'card index dividers onglets' },
|
|
294
|
+
{ e: '📁', t: 'folder dossier' },
|
|
295
|
+
{ e: '📂', t: 'open folder dossier ouvert' },
|
|
296
|
+
{ e: '🗃️', t: 'card box boite fiches' },
|
|
297
|
+
{ e: '🗄️', t: 'file cabinet archive armoire' },
|
|
298
|
+
{ e: '📋', t: 'clipboard presse papier' },
|
|
299
|
+
{ e: '📃', t: 'page curl document' },
|
|
300
|
+
{ e: '📄', t: 'document page' },
|
|
301
|
+
{ e: '📜', t: 'scroll parchemin' },
|
|
302
|
+
{ e: '📰', t: 'newspaper journal presse' },
|
|
303
|
+
{ e: '🗞️', t: 'rolled newspaper journal' },
|
|
304
|
+
{ e: '📓', t: 'notebook carnet cahier' },
|
|
305
|
+
{ e: '📔', t: 'notebook decorated carnet' },
|
|
306
|
+
{ e: '📒', t: 'ledger registre' },
|
|
307
|
+
{ e: '📕', t: 'book rouge red ferme' },
|
|
308
|
+
{ e: '📗', t: 'book vert green' },
|
|
309
|
+
{ e: '📘', t: 'book bleu blue' },
|
|
310
|
+
{ e: '📙', t: 'book orange' },
|
|
311
|
+
{ e: '📚', t: 'books livres bibliotheque' },
|
|
312
|
+
{ e: '📖', t: 'open book livre ouvert' },
|
|
313
|
+
{ e: '🔖', t: 'bookmark marque page signet' },
|
|
314
|
+
{ e: '🏷️', t: 'label tag etiquette' },
|
|
315
|
+
{ e: '💼', t: 'briefcase mallette attache case business' },
|
|
316
|
+
{ e: '🗒️', t: 'spiral note notepad' },
|
|
317
|
+
{ e: '🗓️', t: 'spiral calendar calendrier' },
|
|
318
|
+
{ e: '📅', t: 'calendar calendrier date' },
|
|
319
|
+
{ e: '📆', t: 'tear off calendar calendrier' },
|
|
320
|
+
{ e: '🗑️', t: 'trash poubelle delete supprimer can' },
|
|
321
|
+
{ e: '🪣', t: 'bucket seau' },
|
|
322
|
+
{ e: '📦', t: 'package colis box boite' },
|
|
323
|
+
{ e: '📫', t: 'mailbox boite lettres closed flag up' },
|
|
324
|
+
{ e: '📪', t: 'mailbox boite lettres closed flag down' },
|
|
325
|
+
{ e: '📬', t: 'mailbox open boite lettres flag up' },
|
|
326
|
+
{ e: '📭', t: 'mailbox open boite lettres flag down' },
|
|
327
|
+
{ e: '📮', t: 'postbox boite aux lettres' },
|
|
328
|
+
{ e: '✉️', t: 'envelope enveloppe' },
|
|
329
|
+
{ e: '📧', t: 'email courriel mail at' },
|
|
330
|
+
{ e: '📨', t: 'email incoming recu' },
|
|
331
|
+
{ e: '📩', t: 'email arrow envoi' },
|
|
332
|
+
{ e: '📤', t: 'outbox envoyer sent' },
|
|
333
|
+
{ e: '📥', t: 'inbox recevoir' },
|
|
334
|
+
{ e: '📊', t: 'bar chart graphique barres histogramme' },
|
|
335
|
+
{ e: '📈', t: 'chart up tendance hausse croissance' },
|
|
336
|
+
{ e: '📉', t: 'chart down baisse decroissance' },
|
|
337
|
+
]},
|
|
338
|
+
{ label: 'snippet.emoji_cat_symbols', emojis: [
|
|
339
|
+
{ e: '🆕', t: 'new nouveau nouvelle brand fresh neuf' },
|
|
340
|
+
{ e: '♻️', t: 'existing existant existante recycle recycler reuse reutiliser' },
|
|
341
|
+
{ e: '✅', t: 'check ok valide coche green' },
|
|
342
|
+
{ e: '☑️', t: 'check ballot case' },
|
|
343
|
+
{ e: '✔️', t: 'check coche mark' },
|
|
344
|
+
{ e: '❌', t: 'cross error erreur red rouge croix ko' },
|
|
345
|
+
{ e: '✖️', t: 'multiply multiplier cross' },
|
|
346
|
+
{ e: '❎', t: 'cross square button' },
|
|
347
|
+
{ e: '⚠️', t: 'warning attention danger' },
|
|
348
|
+
{ e: 'ℹ️', t: 'info information' },
|
|
349
|
+
{ e: '❓', t: 'question red interrogation' },
|
|
350
|
+
{ e: '❔', t: 'question white interrogation' },
|
|
351
|
+
{ e: '❗', t: 'exclamation important red' },
|
|
352
|
+
{ e: '❕', t: 'exclamation white' },
|
|
353
|
+
{ e: '‼️', t: 'double exclamation' },
|
|
354
|
+
{ e: '⁉️', t: 'exclamation question' },
|
|
355
|
+
{ e: '🚀', t: 'rocket fusee launch lancer' },
|
|
356
|
+
{ e: '🎉', t: 'party popper tada fete celebration' },
|
|
357
|
+
{ e: '🎊', t: 'confetti ball fete' },
|
|
358
|
+
{ e: '🏆', t: 'trophy trophee winner' },
|
|
359
|
+
{ e: '🥇', t: 'gold medal medaille or first' },
|
|
360
|
+
{ e: '🥈', t: 'silver medal medaille argent second' },
|
|
361
|
+
{ e: '🥉', t: 'bronze medal medaille third' },
|
|
362
|
+
{ e: '🏅', t: 'medal medaille sports' },
|
|
363
|
+
{ e: '🎖️', t: 'military medal medaille' },
|
|
364
|
+
{ e: '🎯', t: 'target cible dart' },
|
|
365
|
+
{ e: '🎁', t: 'gift cadeau present' },
|
|
366
|
+
{ e: '🔔', t: 'bell cloche notify notification' },
|
|
367
|
+
{ e: '🔕', t: 'bell off muted silence' },
|
|
368
|
+
{ e: '📢', t: 'loudspeaker annonce public' },
|
|
369
|
+
{ e: '📣', t: 'megaphone porte voix' },
|
|
370
|
+
{ e: '📯', t: 'postal horn cor' },
|
|
371
|
+
{ e: '➕', t: 'plus add heavy' },
|
|
372
|
+
{ e: '➖', t: 'minus moins heavy' },
|
|
373
|
+
{ e: '➗', t: 'divide division' },
|
|
374
|
+
{ e: '♾️', t: 'infinity infini' },
|
|
375
|
+
{ e: '💲', t: 'dollar sign dollars money argent' },
|
|
376
|
+
{ e: '💱', t: 'currency exchange change devise' },
|
|
377
|
+
{ e: '©️', t: 'copyright' },
|
|
378
|
+
{ e: '®️', t: 'registered' },
|
|
379
|
+
{ e: '™️', t: 'trademark marque' },
|
|
380
|
+
{ e: '↔️', t: 'left right arrow fleche horizontal' },
|
|
381
|
+
{ e: '↕️', t: 'up down arrow fleche vertical' },
|
|
382
|
+
{ e: '↖️', t: 'up left arrow fleche' },
|
|
383
|
+
{ e: '↗️', t: 'up right arrow fleche' },
|
|
384
|
+
{ e: '↘️', t: 'down right arrow fleche' },
|
|
385
|
+
{ e: '↙️', t: 'down left arrow fleche' },
|
|
386
|
+
{ e: '⬅️', t: 'left arrow fleche gauche' },
|
|
387
|
+
{ e: '➡️', t: 'right arrow fleche droite' },
|
|
388
|
+
{ e: '⬆️', t: 'up arrow fleche haut' },
|
|
389
|
+
{ e: '⬇️', t: 'down arrow fleche bas' },
|
|
390
|
+
{ e: '🔃', t: 'clockwise vertical arrows' },
|
|
391
|
+
{ e: '🔄', t: 'counterclockwise arrows reload refresh recharger' },
|
|
392
|
+
{ e: '🔁', t: 'repeat loop boucle' },
|
|
393
|
+
{ e: '🔂', t: 'repeat single once' },
|
|
394
|
+
{ e: '🔀', t: 'shuffle melange random' },
|
|
395
|
+
{ e: '🔼', t: 'up button triangle' },
|
|
396
|
+
{ e: '🔽', t: 'down button triangle' },
|
|
397
|
+
{ e: '⏫', t: 'fast up double triangle' },
|
|
398
|
+
{ e: '⏬', t: 'fast down double triangle' },
|
|
399
|
+
{ e: '⏪', t: 'rewind back' },
|
|
400
|
+
{ e: '⏩', t: 'fast forward avance' },
|
|
401
|
+
{ e: '⏭️', t: 'next track suivant' },
|
|
402
|
+
{ e: '⏮️', t: 'previous track precedent' },
|
|
403
|
+
{ e: '⏯️', t: 'play pause' },
|
|
404
|
+
{ e: '▶️', t: 'play lecture' },
|
|
405
|
+
{ e: '⏸️', t: 'pause' },
|
|
406
|
+
{ e: '⏹️', t: 'stop' },
|
|
407
|
+
{ e: '⏺️', t: 'record enregistrer' },
|
|
408
|
+
{ e: '⏏️', t: 'eject ejecter' },
|
|
409
|
+
{ e: '🎵', t: 'music note musique' },
|
|
410
|
+
{ e: '🎶', t: 'music notes musique' },
|
|
411
|
+
{ e: '🔈', t: 'speaker low bas' },
|
|
412
|
+
{ e: '🔉', t: 'speaker medium moyen' },
|
|
413
|
+
{ e: '🔊', t: 'speaker loud fort haut son sound' },
|
|
414
|
+
{ e: '🔇', t: 'muted mute silence' },
|
|
415
|
+
{ e: '📶', t: 'signal antenne bars' },
|
|
416
|
+
{ e: '🛜', t: 'wireless wifi' },
|
|
417
|
+
{ e: '🔗', t: 'link lien chain' },
|
|
418
|
+
{ e: '⛓️', t: 'chains chaine' },
|
|
419
|
+
{ e: '🏁', t: 'finish flag checkered fin arrivee' },
|
|
420
|
+
{ e: '🚩', t: 'flag drapeau triangular' },
|
|
421
|
+
{ e: '🏳️', t: 'white flag drapeau blanc' },
|
|
422
|
+
{ e: '🏴', t: 'black flag drapeau noir' },
|
|
423
|
+
{ e: '🏳️🌈', t: 'rainbow lgbt pride drapeau arc en ciel' },
|
|
424
|
+
{ e: '🏳️⚧️', t: 'trans drapeau transgender' },
|
|
425
|
+
{ e: '🏴☠️', t: 'pirate drapeau jolly roger' },
|
|
426
|
+
]},
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
let _emojiGridWired = false;
|
|
430
|
+
|
|
431
|
+
function _emojiBtnHtml(item) {
|
|
432
|
+
return `<button type="button" data-emoji="${item.e}" title="${item.t.split(' ').slice(0, 3).join(', ')}" class="emoji-btn w-7 h-7 text-lg rounded hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center justify-center">${item.e}</button>`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function _renderEmojiCategories() {
|
|
436
|
+
return _EMOJI_CATEGORIES.map((cat) => `
|
|
437
|
+
<div>
|
|
438
|
+
<div class="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-1 px-1">${window.t(cat.label)}</div>
|
|
439
|
+
<div class="flex flex-wrap gap-1">
|
|
440
|
+
${cat.emojis.map(_emojiBtnHtml).join('')}
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
443
|
+
`).join('');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function _renderEmojiSearch(query) {
|
|
447
|
+
const q = query.toLowerCase().trim();
|
|
448
|
+
const matches = [];
|
|
449
|
+
for (const cat of _EMOJI_CATEGORIES) {
|
|
450
|
+
for (const item of cat.emojis) {
|
|
451
|
+
if (item.t.includes(q) || item.e === query) matches.push(item);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (matches.length === 0) {
|
|
455
|
+
return `<div class="text-xs text-gray-400 dark:text-gray-600 px-1 py-2">${window.t('snippet.emoji_no_results')}</div>`;
|
|
456
|
+
}
|
|
457
|
+
return `<div class="flex flex-wrap gap-1">${matches.map(_emojiBtnHtml).join('')}</div>`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function emojiInit() {
|
|
461
|
+
const grid = document.getElementById('snip-emoji-grid');
|
|
462
|
+
if (!grid) return;
|
|
463
|
+
grid.innerHTML = _renderEmojiCategories();
|
|
464
|
+
|
|
465
|
+
if (!_emojiGridWired) {
|
|
466
|
+
grid.addEventListener('click', (e) => {
|
|
467
|
+
const btn = e.target.closest('.emoji-btn');
|
|
468
|
+
if (btn && btn.dataset.emoji) emojiAppend(btn.dataset.emoji);
|
|
469
|
+
});
|
|
470
|
+
const searchInput = document.getElementById('snip-emoji-search');
|
|
471
|
+
if (searchInput) {
|
|
472
|
+
searchInput.addEventListener('input', (e) => emojiFilter(e.target.value));
|
|
473
|
+
}
|
|
474
|
+
_emojiGridWired = true;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const searchInput = document.getElementById('snip-emoji-search');
|
|
478
|
+
if (searchInput) searchInput.value = '';
|
|
479
|
+
snippetUpdatePreview();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function emojiFilter(query) {
|
|
483
|
+
const grid = document.getElementById('snip-emoji-grid');
|
|
484
|
+
if (!grid) return;
|
|
485
|
+
const q = (query || '').trim();
|
|
486
|
+
grid.innerHTML = q.length < 2 ? _renderEmojiCategories() : _renderEmojiSearch(q);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function emojiAppend(emoji) {
|
|
490
|
+
const input = document.getElementById('snip-emoji-string');
|
|
491
|
+
if (!input) return;
|
|
492
|
+
input.value += emoji;
|
|
493
|
+
snippetUpdatePreview();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function emojiClear() {
|
|
497
|
+
const input = document.getElementById('snip-emoji-string');
|
|
498
|
+
if (!input) return;
|
|
499
|
+
input.value = '';
|
|
500
|
+
snippetUpdatePreview();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const _COLOR_SWATCHES = {
|
|
504
|
+
info: { bg: "#eff6ff", border: "#3b82f6", text: "#1e3a5f" },
|
|
505
|
+
success: { bg: "#f0fdf4", border: "#22c55e", text: "#14532d" },
|
|
506
|
+
warning: { bg: "#fffbeb", border: "#f59e0b", text: "#451a03" },
|
|
507
|
+
danger: { bg: "#fef2f2", border: "#ef4444", text: "#450a0a" },
|
|
508
|
+
note: { bg: "#f5f3ff", border: "#8b5cf6", text: "#2e1065" },
|
|
509
|
+
neutral: { bg: "#f9fafb", border: "#6b7280", text: "#111827" },
|
|
510
|
+
};
|
|
511
|
+
let _colorSectionSwatch = "info";
|
|
512
|
+
let _colorTextSwatch = "info";
|
|
513
|
+
|
|
514
|
+
function colorSectionPickSwatch(btn) {
|
|
515
|
+
document.querySelectorAll(".color-swatch-btn").forEach((b) => {
|
|
516
|
+
b.classList.remove("selected-swatch", "ring-offset-2");
|
|
517
|
+
});
|
|
518
|
+
btn.classList.add("selected-swatch", "ring-offset-2");
|
|
519
|
+
const color = btn.getAttribute("data-color-swatch");
|
|
520
|
+
// Apply the matching ring color
|
|
521
|
+
const ringMap = { info: "ring-blue-400", success: "ring-green-400", warning: "ring-amber-400", danger: "ring-red-400", note: "ring-purple-400", neutral: "ring-gray-400" };
|
|
522
|
+
btn.classList.add(ringMap[color] || "ring-blue-400");
|
|
523
|
+
_colorSectionSwatch = color;
|
|
524
|
+
snippetUpdatePreview();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function colorTextPickSwatch(btn) {
|
|
528
|
+
document.querySelectorAll(".color-text-swatch-btn").forEach((b) => {
|
|
529
|
+
b.classList.remove("selected-text-swatch", "ring-offset-2");
|
|
530
|
+
});
|
|
531
|
+
btn.classList.add("selected-text-swatch", "ring-offset-2");
|
|
532
|
+
const color = btn.getAttribute("data-color-text-swatch");
|
|
533
|
+
const ringMap = { info: "ring-blue-400", success: "ring-green-400", warning: "ring-amber-400", danger: "ring-red-400", note: "ring-purple-400", neutral: "ring-gray-400" };
|
|
534
|
+
btn.classList.add(ringMap[color] || "ring-blue-400");
|
|
535
|
+
_colorTextSwatch = color;
|
|
536
|
+
snippetUpdatePreview();
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function _stripMdInline(s) {
|
|
540
|
+
return s
|
|
541
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
|
542
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
543
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
544
|
+
.replace(/__([^_]+)__/g, "$1")
|
|
545
|
+
.replace(/(^|[^*])\*([^*]+)\*(?!\*)/g, "$1$2")
|
|
546
|
+
.replace(/(^|[^_])_([^_]+)_(?!_)/g, "$1$2")
|
|
547
|
+
.trim();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function _slugifyHeading(text) {
|
|
551
|
+
return text
|
|
552
|
+
.toLowerCase()
|
|
553
|
+
.replace(/[^\w\s-]/g, "")
|
|
554
|
+
.trim()
|
|
555
|
+
.replace(/\s+/g, "-");
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function _extractHeadingsFromMarkdown(content) {
|
|
559
|
+
const out = [];
|
|
560
|
+
const lines = (content || "").split("\n");
|
|
561
|
+
let inFence = false;
|
|
562
|
+
for (const line of lines) {
|
|
563
|
+
if (/^```/.test(line)) {
|
|
564
|
+
inFence = !inFence;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
if (inFence) continue;
|
|
568
|
+
const m = line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/);
|
|
569
|
+
if (!m) continue;
|
|
570
|
+
const text = _stripMdInline(m[2]);
|
|
571
|
+
const slug = _slugifyHeading(text);
|
|
572
|
+
if (slug) out.push({ level: m[1].length, text, slug });
|
|
573
|
+
}
|
|
574
|
+
return out;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function _collectEditorHeadings() {
|
|
578
|
+
const editor = document.getElementById("doc-editor");
|
|
579
|
+
return _extractHeadingsFromMarkdown(editor ? editor.value : "");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function _renderAnchorOptions(sel, headings, emptyKey) {
|
|
583
|
+
if (!sel) return;
|
|
584
|
+
if (headings.length === 0) {
|
|
585
|
+
sel.innerHTML = `<option value="" disabled selected>${window.t(emptyKey)}</option>`;
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
sel.innerHTML = headings
|
|
589
|
+
.map((h) => {
|
|
590
|
+
const indent = "· ".repeat(Math.max(0, h.level - 1));
|
|
591
|
+
return `<option value="${esc(h.slug)}">${esc(indent + h.text)}</option>`;
|
|
592
|
+
})
|
|
593
|
+
.join("");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function _populateAnchorSelect() {
|
|
597
|
+
_renderAnchorOptions(
|
|
598
|
+
document.getElementById("snip-anchor-id"),
|
|
599
|
+
_collectEditorHeadings(),
|
|
600
|
+
'snippet.link_anchor_no_headings',
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async function snippetAnchorDocChanged() {
|
|
605
|
+
const docSel = document.getElementById("snip-anchor-doc-select");
|
|
606
|
+
const anchorSel = document.getElementById("snip-anchor-doc-id");
|
|
607
|
+
if (!docSel || !anchorSel) return;
|
|
608
|
+
const docId = docSel.value;
|
|
609
|
+
if (!docId) {
|
|
610
|
+
_renderAnchorOptions(anchorSel, [], 'snippet.link_anchor_no_headings');
|
|
611
|
+
snippetUpdatePreview();
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
anchorSel.innerHTML = `<option value="" disabled selected>${window.t('common.loading')}</option>`;
|
|
615
|
+
try {
|
|
616
|
+
const doc = await fetch("/api/documents/" + encodeURIComponent(docId))
|
|
617
|
+
.then((r) => {
|
|
618
|
+
if (!r.ok) throw new Error(r.statusText);
|
|
619
|
+
return r.json();
|
|
620
|
+
});
|
|
621
|
+
const headings = _extractHeadingsFromMarkdown(doc.content || "");
|
|
622
|
+
_renderAnchorOptions(anchorSel, headings, 'snippet.link_anchor_no_headings');
|
|
623
|
+
} catch {
|
|
624
|
+
_renderAnchorOptions(anchorSel, [], 'snippet.link_anchor_no_headings');
|
|
625
|
+
}
|
|
626
|
+
snippetUpdatePreview();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function openSnippetsModal() {
|
|
630
|
+
const editor = document.getElementById("doc-editor");
|
|
631
|
+
_snippetSelStart = editor.selectionStart;
|
|
632
|
+
_snippetSelEnd = editor.selectionEnd;
|
|
633
|
+
|
|
634
|
+
const docOpts = allDocs
|
|
635
|
+
.map((d) => `<option value="${d.id}">${d.title}</option>`)
|
|
636
|
+
.join("");
|
|
637
|
+
document.getElementById("snip-doc-select").innerHTML = docOpts;
|
|
638
|
+
document.getElementById("snip-anchor-doc-select").innerHTML = docOpts;
|
|
639
|
+
_populateAnchorSelect();
|
|
640
|
+
snippetAnchorDocChanged();
|
|
641
|
+
|
|
642
|
+
const msgEl = document.getElementById("snippet-detect-msg");
|
|
643
|
+
const selectedText = editor.value.slice(
|
|
644
|
+
_snippetSelStart,
|
|
645
|
+
_snippetSelEnd,
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
if (selectedText) {
|
|
649
|
+
const detected = detectSnippetType(selectedText);
|
|
650
|
+
if (detected) {
|
|
651
|
+
document.getElementById("snippet-type").value = detected;
|
|
652
|
+
snippetTypeChanged();
|
|
653
|
+
parseAndFillSnippet(selectedText, detected);
|
|
654
|
+
const labels = {
|
|
655
|
+
collapsible: window.t('snippet.collapsible'),
|
|
656
|
+
link: window.t('snippet.link'),
|
|
657
|
+
"doc-link": window.t('snippet.link_doc'),
|
|
658
|
+
"anchor-link": window.t('snippet.link_anchor'),
|
|
659
|
+
"anchor-doc-link": window.t('snippet.link_doc_anchor'),
|
|
660
|
+
"ordered-list": window.t('snippet.numbered_list'),
|
|
661
|
+
"unordered-list": window.t('snippet.bullet_list'),
|
|
662
|
+
"code-block": window.t('snippet.code_block'),
|
|
663
|
+
blockquote: window.t('snippet.blockquote'),
|
|
664
|
+
separator: window.t('snippet.separator'),
|
|
665
|
+
image: window.t('snippet.image'),
|
|
666
|
+
table: window.t('snippet.table'),
|
|
667
|
+
tree: window.t('snippet.tree'),
|
|
668
|
+
"colored-section": window.t('snippet.colored_section'),
|
|
669
|
+
"colored-text": window.t('snippet.colored_text'),
|
|
670
|
+
};
|
|
671
|
+
msgEl.textContent = window.t('snippet.detected_msg').replace('{type}', labels[detected] ?? detected);
|
|
672
|
+
msgEl.className =
|
|
673
|
+
"rounded-lg px-3 py-2 text-xs bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 border border-green-200 dark:border-green-800";
|
|
674
|
+
} else {
|
|
675
|
+
document.getElementById("snippet-type").value = "diagram";
|
|
676
|
+
snippetTypeChanged();
|
|
677
|
+
msgEl.textContent = window.t('snippet.unknown_type_msg');
|
|
678
|
+
msgEl.className =
|
|
679
|
+
"rounded-lg px-3 py-2 text-xs bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-800";
|
|
680
|
+
}
|
|
681
|
+
msgEl.classList.remove("hidden");
|
|
682
|
+
} else {
|
|
683
|
+
msgEl.classList.add("hidden");
|
|
684
|
+
document.getElementById("snippet-type").value = "diagram";
|
|
685
|
+
snippetTypeChanged();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
document.getElementById("snippets-modal").classList.remove("hidden");
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function closeSnippetsModal() {
|
|
692
|
+
document.getElementById("snippets-modal").classList.add("hidden");
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function snippetTypeChanged() {
|
|
696
|
+
const type = document.getElementById("snippet-type").value;
|
|
697
|
+
_SNIPPET_PANELS.forEach((p) => {
|
|
698
|
+
const panel = document.getElementById("snip-panel-" + p);
|
|
699
|
+
if (panel) panel.classList.toggle("hidden", p !== type);
|
|
700
|
+
});
|
|
701
|
+
const previewWrap = document.getElementById("snippet-preview")?.parentElement;
|
|
702
|
+
if (previewWrap) previewWrap.classList.toggle("hidden", type === "attachment");
|
|
703
|
+
|
|
704
|
+
if (type === "table") tableInit();
|
|
705
|
+
else if (type === "tree") treeInit();
|
|
706
|
+
else if (type === "diagram") snippetDiagInit();
|
|
707
|
+
else if (type === "emojis") emojiInit();
|
|
708
|
+
else if (type === "attachment") {
|
|
709
|
+
/* no preview — file picker opens on Insert */
|
|
710
|
+
} else snippetUpdatePreview();
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
async function snippetDiagInit() {
|
|
714
|
+
let diagrams = [];
|
|
715
|
+
try {
|
|
716
|
+
diagrams = await fetch("/api/diagrams").then((r) => r.json());
|
|
717
|
+
} catch {
|
|
718
|
+
diagrams = [];
|
|
719
|
+
}
|
|
720
|
+
const sel = document.getElementById("snip-diag-select");
|
|
721
|
+
sel.innerHTML = diagrams.length
|
|
722
|
+
? diagrams
|
|
723
|
+
.map(
|
|
724
|
+
(d) => `<option value="${esc(d.id)}">${esc(d.title)}</option>`,
|
|
725
|
+
)
|
|
726
|
+
.join("")
|
|
727
|
+
: `<option value="" disabled>${window.t('snippet.diagram_no_diagrams')}</option>`;
|
|
728
|
+
|
|
729
|
+
// Pre-fill image name from current doc title
|
|
730
|
+
const docTitle = document
|
|
731
|
+
.getElementById("doc-title")
|
|
732
|
+
.textContent.trim();
|
|
733
|
+
const slug = docTitle
|
|
734
|
+
.toLowerCase()
|
|
735
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
736
|
+
.replace(/^_|_$/g, "");
|
|
737
|
+
document.getElementById("snip-diag-img-name").value = slug
|
|
738
|
+
? slug + ".png"
|
|
739
|
+
: "diagram.png";
|
|
740
|
+
document.getElementById("snip-diag-new-name").value = docTitle
|
|
741
|
+
? docTitle + " Diagram"
|
|
742
|
+
: "";
|
|
743
|
+
|
|
744
|
+
document.getElementById("snip-diag-mode-existing").checked = true;
|
|
745
|
+
snippetDiagModeChanged();
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function snippetDiagModeChanged() {
|
|
749
|
+
const isNew = document.getElementById("snip-diag-mode-new").checked;
|
|
750
|
+
document
|
|
751
|
+
.getElementById("snip-diag-existing-section")
|
|
752
|
+
.classList.toggle("hidden", isNew);
|
|
753
|
+
document
|
|
754
|
+
.getElementById("snip-diag-new-section")
|
|
755
|
+
.classList.toggle("hidden", !isNew);
|
|
756
|
+
snippetDiagSyncImgName();
|
|
757
|
+
snippetUpdatePreview();
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function snippetDiagSyncImgName() {
|
|
761
|
+
const isNew = document.getElementById("snip-diag-mode-new").checked;
|
|
762
|
+
let label;
|
|
763
|
+
if (isNew) {
|
|
764
|
+
label = document.getElementById("snip-diag-new-name").value.trim();
|
|
765
|
+
} else {
|
|
766
|
+
const sel = document.getElementById("snip-diag-select");
|
|
767
|
+
label = sel.options[sel.selectedIndex]?.text ?? "";
|
|
768
|
+
}
|
|
769
|
+
const slug = label
|
|
770
|
+
.toLowerCase()
|
|
771
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
772
|
+
.replace(/^_|_$/g, "");
|
|
773
|
+
document.getElementById("snip-diag-img-name").value = slug
|
|
774
|
+
? slug + ".png"
|
|
775
|
+
: "diagram.png";
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function buildSnippetMarkdown() {
|
|
779
|
+
const type = document.getElementById("snippet-type").value;
|
|
780
|
+
switch (type) {
|
|
781
|
+
case "collapsible": {
|
|
782
|
+
const summary =
|
|
783
|
+
document.getElementById("snip-collapsible-summary").value ||
|
|
784
|
+
window.t('snippet.collapsible_summary_value');
|
|
785
|
+
return `<details>\n<summary>${summary}</summary>\n\n## Titre\n\nTexte\n\n</details>`;
|
|
786
|
+
}
|
|
787
|
+
case "link": {
|
|
788
|
+
const text =
|
|
789
|
+
document.getElementById("snip-link-text").value ||
|
|
790
|
+
window.t('snippet.link_text_placeholder');
|
|
791
|
+
const url =
|
|
792
|
+
document.getElementById("snip-link-url").value || "https://...";
|
|
793
|
+
return `<a href="${url}" target="_blank">${text}</a>`; // [${text}](${url})
|
|
794
|
+
}
|
|
795
|
+
case "doc-link": {
|
|
796
|
+
const sel = document.getElementById("snip-doc-select");
|
|
797
|
+
const docId = sel.value;
|
|
798
|
+
const customText =
|
|
799
|
+
document.getElementById("snip-doc-link-text").value;
|
|
800
|
+
const docTitle = sel.options[sel.selectedIndex]?.text ?? docId;
|
|
801
|
+
const text = customText || docTitle;
|
|
802
|
+
return `[${text}](?doc=${encodeURIComponent(docId)})`;
|
|
803
|
+
}
|
|
804
|
+
case "anchor-link": {
|
|
805
|
+
const text =
|
|
806
|
+
document.getElementById("snip-anchor-text").value ||
|
|
807
|
+
window.t('snippet.link_section_placeholder');
|
|
808
|
+
const anchor =
|
|
809
|
+
document.getElementById("snip-anchor-id").value ||
|
|
810
|
+
window.t('snippet.link_anchor_placeholder');
|
|
811
|
+
return `[${text}](#${anchor})`;
|
|
812
|
+
}
|
|
813
|
+
case "anchor-doc-link": {
|
|
814
|
+
const sel = document.getElementById("snip-anchor-doc-select");
|
|
815
|
+
const docId = sel.value;
|
|
816
|
+
const text =
|
|
817
|
+
document.getElementById("snip-anchor-doc-text").value ||
|
|
818
|
+
window.t('snippet.link_section_placeholder');
|
|
819
|
+
const anchor =
|
|
820
|
+
document.getElementById("snip-anchor-doc-id").value ||
|
|
821
|
+
window.t('snippet.link_anchor_placeholder');
|
|
822
|
+
return `[${text}](?doc=${encodeURIComponent(docId)}#${anchor})`;
|
|
823
|
+
}
|
|
824
|
+
case "ordered-list":
|
|
825
|
+
return [
|
|
826
|
+
"1. Élément 1",
|
|
827
|
+
"2. Élément 2",
|
|
828
|
+
" 1. Sous-élément 2.1",
|
|
829
|
+
" 2. Sous-élément 2.2",
|
|
830
|
+
"3. Élément 3",
|
|
831
|
+
" 1. Sous-élément 3.1",
|
|
832
|
+
" 1. Sous-sous-élément 3.1.1",
|
|
833
|
+
].join("\n");
|
|
834
|
+
case "unordered-list":
|
|
835
|
+
return [
|
|
836
|
+
"- Élément 1",
|
|
837
|
+
"- Élément 2",
|
|
838
|
+
" - Sous-élément 2.1",
|
|
839
|
+
" - Sous-élément 2.2",
|
|
840
|
+
"- Élément 3",
|
|
841
|
+
" - Sous-élément 3.1",
|
|
842
|
+
" - Sous-sous-élément 3.1.1",
|
|
843
|
+
].join("\n");
|
|
844
|
+
case "code-block": {
|
|
845
|
+
const lang = document.getElementById("snip-code-lang").value || "";
|
|
846
|
+
return `\`\`\`${lang}\n// code ici\n\`\`\``;
|
|
847
|
+
}
|
|
848
|
+
case "blockquote":
|
|
849
|
+
return `> Citation ici\n>\n> — Auteur`;
|
|
850
|
+
case "separator":
|
|
851
|
+
return `\n---\n`;
|
|
852
|
+
case "image": {
|
|
853
|
+
const alt =
|
|
854
|
+
document.getElementById("snip-image-alt").value || "image";
|
|
855
|
+
const url =
|
|
856
|
+
document.getElementById("snip-image-url").value ||
|
|
857
|
+
"./images/mon-image.png";
|
|
858
|
+
return ``;
|
|
859
|
+
}
|
|
860
|
+
case "table":
|
|
861
|
+
return buildTableMarkdown();
|
|
862
|
+
case "tree":
|
|
863
|
+
return buildTreeMarkdown();
|
|
864
|
+
case "diagram": {
|
|
865
|
+
const isNew = document.getElementById("snip-diag-mode-new").checked;
|
|
866
|
+
const imgName =
|
|
867
|
+
document.getElementById("snip-diag-img-name").value.trim() ||
|
|
868
|
+
"diagram.png";
|
|
869
|
+
let diagId, diagLabel;
|
|
870
|
+
if (isNew) {
|
|
871
|
+
diagId = "d" + Date.now();
|
|
872
|
+
diagLabel =
|
|
873
|
+
document.getElementById("snip-diag-new-name").value.trim() ||
|
|
874
|
+
"Diagram";
|
|
875
|
+
} else {
|
|
876
|
+
const sel = document.getElementById("snip-diag-select");
|
|
877
|
+
diagId = sel.value;
|
|
878
|
+
diagLabel = sel.options[sel.selectedIndex]?.text || "Diagram";
|
|
879
|
+
}
|
|
880
|
+
return `[](/diagram?id=${diagId})`;
|
|
881
|
+
}
|
|
882
|
+
case "colored-text": {
|
|
883
|
+
const c = _COLOR_SWATCHES[_colorTextSwatch] || _COLOR_SWATCHES.info;
|
|
884
|
+
const content = document.getElementById("snip-colored-text-content").value || window.t('snippet.colored_text_content_placeholder');
|
|
885
|
+
return `<span style="color:${c.border};">${content}</span>`;
|
|
886
|
+
}
|
|
887
|
+
case "colored-section": {
|
|
888
|
+
const c = _COLOR_SWATCHES[_colorSectionSwatch] || _COLOR_SWATCHES.info;
|
|
889
|
+
const content = document.getElementById("snip-colored-content").value || window.t('snippet.colored_section_content_placeholder');
|
|
890
|
+
return `<div style="background:${c.bg};border-left:4px solid ${c.border};color:${c.text};padding:1rem 1.25rem;border-radius:0.375rem;margin:1rem 0;">\n\n${content}\n\n</div>`;
|
|
891
|
+
}
|
|
892
|
+
case "emojis":
|
|
893
|
+
return document.getElementById("snip-emoji-string").value;
|
|
894
|
+
case "local-search":
|
|
895
|
+
return `<div data-ld-local-search></div>`;
|
|
896
|
+
default:
|
|
897
|
+
return "";
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function snippetUpdatePreview() {
|
|
902
|
+
document.getElementById("snippet-preview").textContent =
|
|
903
|
+
buildSnippetMarkdown();
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function insertSnippet() {
|
|
907
|
+
const type = document.getElementById("snippet-type").value;
|
|
908
|
+
if (type === "diagram") {
|
|
909
|
+
insertDiagramSnippet();
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (type === "attachment") {
|
|
913
|
+
closeSnippetsModal();
|
|
914
|
+
if (typeof openFilePicker === "function") openFilePicker();
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const text = buildSnippetMarkdown();
|
|
918
|
+
closeSnippetsModal();
|
|
919
|
+
const editor = document.getElementById("doc-editor");
|
|
920
|
+
const before = editor.value.slice(0, _snippetSelStart);
|
|
921
|
+
const after = editor.value.slice(_snippetSelEnd);
|
|
922
|
+
editor.value = before + text + after;
|
|
923
|
+
editor.selectionStart = editor.selectionEnd =
|
|
924
|
+
_snippetSelStart + text.length;
|
|
925
|
+
editor.focus();
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
async function insertDiagramSnippet() {
|
|
929
|
+
const isNew = document.getElementById("snip-diag-mode-new").checked;
|
|
930
|
+
const imgName =
|
|
931
|
+
document.getElementById("snip-diag-img-name").value.trim() ||
|
|
932
|
+
"diagram.png";
|
|
933
|
+
let diagId, diagLabel;
|
|
934
|
+
if (isNew) {
|
|
935
|
+
diagId = "d" + Date.now();
|
|
936
|
+
diagLabel =
|
|
937
|
+
document.getElementById("snip-diag-new-name").value.trim() ||
|
|
938
|
+
"Diagram";
|
|
939
|
+
try {
|
|
940
|
+
await fetch(`/api/diagrams/${diagId}`, {
|
|
941
|
+
method: "PUT",
|
|
942
|
+
headers: { "Content-Type": "application/json" },
|
|
943
|
+
body: JSON.stringify({ title: diagLabel, nodes: [], edges: [] }),
|
|
944
|
+
});
|
|
945
|
+
} catch (err) {
|
|
946
|
+
alert(window.t('error.create_diagram') + err.message);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
} else {
|
|
950
|
+
const sel = document.getElementById("snip-diag-select");
|
|
951
|
+
diagId = sel.value;
|
|
952
|
+
diagLabel = sel.options[sel.selectedIndex]?.text || "Diagram";
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Insert at cursor
|
|
956
|
+
const md = `[](/diagram?id=${diagId})`;
|
|
957
|
+
closeSnippetsModal();
|
|
958
|
+
const editor = document.getElementById("doc-editor");
|
|
959
|
+
const before = editor.value.slice(0, _snippetSelStart);
|
|
960
|
+
const after = editor.value.slice(_snippetSelEnd);
|
|
961
|
+
editor.value = before + md + after;
|
|
962
|
+
|
|
963
|
+
// Auto-save then redirect to diagram editor
|
|
964
|
+
try {
|
|
965
|
+
const newContent = editor.value;
|
|
966
|
+
const res = await fetch("/api/documents/" + currentDocId, {
|
|
967
|
+
method: "PUT",
|
|
968
|
+
headers: { "Content-Type": "application/json" },
|
|
969
|
+
body: JSON.stringify({ content: newContent }),
|
|
970
|
+
});
|
|
971
|
+
if (!res.ok) throw new Error(await res.text());
|
|
972
|
+
currentDocContent = newContent;
|
|
973
|
+
} catch (err) {
|
|
974
|
+
alert("Erreur lors de la sauvegarde : " + err.message);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
window.location.href = `/diagram?id=${diagId}&img=${encodeURIComponent(imgName)}`;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// ── Snippet parsing (detection lives in /snippet-detect.js) ────────────────
|
|
982
|
+
function parseAndFillSnippet(text, type) {
|
|
983
|
+
const t = text.trim();
|
|
984
|
+
switch (type) {
|
|
985
|
+
case "collapsible": {
|
|
986
|
+
const m = t.match(/<summary>([\s\S]*?)<\/summary>/i);
|
|
987
|
+
if (m)
|
|
988
|
+
document.getElementById("snip-collapsible-summary").value =
|
|
989
|
+
m[1].trim();
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
case "link": {
|
|
993
|
+
const m = t.match(/^\[([\s\S]*?)\]\(([\s\S]*?)\)$/);
|
|
994
|
+
if (m) {
|
|
995
|
+
document.getElementById("snip-link-text").value = m[1];
|
|
996
|
+
document.getElementById("snip-link-url").value = m[2];
|
|
997
|
+
}
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
case "doc-link": {
|
|
1001
|
+
const m = t.match(/^\[([\s\S]*?)\]\(\?doc=([\s\S]*?)\)$/);
|
|
1002
|
+
if (m) {
|
|
1003
|
+
const docId = decodeURIComponent(m[1] === "" ? m[2] : m[2]);
|
|
1004
|
+
const sel = document.getElementById("snip-doc-select");
|
|
1005
|
+
for (const opt of sel.options) {
|
|
1006
|
+
if (opt.value === decodeURIComponent(m[2])) {
|
|
1007
|
+
sel.value = opt.value;
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const autoTitle = sel.options[sel.selectedIndex]?.text ?? "";
|
|
1012
|
+
document.getElementById("snip-doc-link-text").value =
|
|
1013
|
+
m[1] === autoTitle ? "" : m[1];
|
|
1014
|
+
}
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
case "anchor-link": {
|
|
1018
|
+
const m = t.match(/^\[([\s\S]*?)\]\(#([\s\S]*?)\)$/);
|
|
1019
|
+
if (m) {
|
|
1020
|
+
document.getElementById("snip-anchor-text").value = m[1];
|
|
1021
|
+
const sel = document.getElementById("snip-anchor-id");
|
|
1022
|
+
const wanted = m[2];
|
|
1023
|
+
const hasOpt = Array.from(sel.options).some(
|
|
1024
|
+
(o) => o.value === wanted,
|
|
1025
|
+
);
|
|
1026
|
+
if (!hasOpt) {
|
|
1027
|
+
const opt = document.createElement("option");
|
|
1028
|
+
opt.value = wanted;
|
|
1029
|
+
opt.textContent = wanted;
|
|
1030
|
+
sel.insertBefore(opt, sel.firstChild);
|
|
1031
|
+
}
|
|
1032
|
+
sel.value = wanted;
|
|
1033
|
+
}
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
1036
|
+
case "anchor-doc-link": {
|
|
1037
|
+
const m = t.match(
|
|
1038
|
+
/^\[([\s\S]*?)\]\(\?doc=([\s\S]*?)#([\s\S]*?)\)$/,
|
|
1039
|
+
);
|
|
1040
|
+
if (m) {
|
|
1041
|
+
const sel = document.getElementById("snip-anchor-doc-select");
|
|
1042
|
+
for (const opt of sel.options) {
|
|
1043
|
+
if (opt.value === decodeURIComponent(m[2])) {
|
|
1044
|
+
sel.value = opt.value;
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
document.getElementById("snip-anchor-doc-text").value = m[1];
|
|
1049
|
+
const wanted = m[3];
|
|
1050
|
+
snippetAnchorDocChanged().then(() => {
|
|
1051
|
+
const anchorSel = document.getElementById("snip-anchor-doc-id");
|
|
1052
|
+
if (!anchorSel) return;
|
|
1053
|
+
const hasOpt = Array.from(anchorSel.options).some(
|
|
1054
|
+
(o) => o.value === wanted,
|
|
1055
|
+
);
|
|
1056
|
+
if (!hasOpt) {
|
|
1057
|
+
const opt = document.createElement("option");
|
|
1058
|
+
opt.value = wanted;
|
|
1059
|
+
opt.textContent = wanted;
|
|
1060
|
+
anchorSel.insertBefore(opt, anchorSel.firstChild);
|
|
1061
|
+
}
|
|
1062
|
+
anchorSel.value = wanted;
|
|
1063
|
+
snippetUpdatePreview();
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
break;
|
|
1067
|
+
}
|
|
1068
|
+
case "code-block": {
|
|
1069
|
+
const m = t.match(/^```(\w*)\n/);
|
|
1070
|
+
document.getElementById("snip-code-lang").value = m ? m[1] : "";
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
case "image": {
|
|
1074
|
+
const m = t.match(/^!\[([\s\S]*?)\]\(([\s\S]*?)\)$/);
|
|
1075
|
+
if (m) {
|
|
1076
|
+
document.getElementById("snip-image-alt").value = m[1];
|
|
1077
|
+
document.getElementById("snip-image-url").value = m[2];
|
|
1078
|
+
}
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
case "table": {
|
|
1082
|
+
const allLines = t
|
|
1083
|
+
.split("\n")
|
|
1084
|
+
.filter((l) => /^\|.*\|$/.test(l.trim()));
|
|
1085
|
+
const dataLines = allLines.filter(
|
|
1086
|
+
(l) => !/^\| *[-: ][-| :]*\|/.test(l),
|
|
1087
|
+
);
|
|
1088
|
+
_tableData = dataLines.map((line) =>
|
|
1089
|
+
line
|
|
1090
|
+
.split("|")
|
|
1091
|
+
.slice(1, -1)
|
|
1092
|
+
.map((c) => c.trim()),
|
|
1093
|
+
);
|
|
1094
|
+
const maxCols = Math.max(..._tableData.map((r) => r.length));
|
|
1095
|
+
_tableData.forEach((row) => {
|
|
1096
|
+
while (row.length < maxCols) row.push("");
|
|
1097
|
+
});
|
|
1098
|
+
tableRenderGrid();
|
|
1099
|
+
break;
|
|
1100
|
+
}
|
|
1101
|
+
case "tree": {
|
|
1102
|
+
const inner = t.replace(/^```text\n/, "").replace(/\n```$/, "");
|
|
1103
|
+
_treeItems = inner.split("\n").map((line) => {
|
|
1104
|
+
const m = line.match(/^((?:│ | )*)(?:├── |└── )([\s\S]+)$/);
|
|
1105
|
+
if (m) return { name: m[2], depth: m[1].length / 4 + 1 };
|
|
1106
|
+
return { name: line, depth: 0 };
|
|
1107
|
+
});
|
|
1108
|
+
treeRenderList();
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
1111
|
+
case "colored-text": {
|
|
1112
|
+
const m = t.match(/^<span\s[^>]*color:([^;>"]+)[^>]*>([\s\S]*)<\/span>$/);
|
|
1113
|
+
if (m) {
|
|
1114
|
+
const col = m[1].trim();
|
|
1115
|
+
const found = Object.entries(_COLOR_SWATCHES).find(([, v]) => v.border === col);
|
|
1116
|
+
if (found) {
|
|
1117
|
+
_colorTextSwatch = found[0];
|
|
1118
|
+
document.querySelectorAll(".color-text-swatch-btn").forEach((b) => b.classList.remove("selected-text-swatch", "ring-offset-2"));
|
|
1119
|
+
const activeBtn = document.querySelector(`[data-color-text-swatch="${_colorTextSwatch}"]`);
|
|
1120
|
+
if (activeBtn) activeBtn.classList.add("selected-text-swatch", "ring-offset-2");
|
|
1121
|
+
}
|
|
1122
|
+
document.getElementById("snip-colored-text-content").value = m[2];
|
|
1123
|
+
}
|
|
1124
|
+
break;
|
|
1125
|
+
}
|
|
1126
|
+
case "colored-section": {
|
|
1127
|
+
// Extract border color from inline style to guess the swatch
|
|
1128
|
+
const borderM = t.match(/border-left:[^;]*solid\s+(#[0-9a-fA-F]{6})/);
|
|
1129
|
+
if (borderM) {
|
|
1130
|
+
const found = Object.entries(_COLOR_SWATCHES).find(([, v]) => v.border === borderM[1]);
|
|
1131
|
+
if (found) {
|
|
1132
|
+
_colorSectionSwatch = found[0];
|
|
1133
|
+
document.querySelectorAll(".color-swatch-btn").forEach((b) => {
|
|
1134
|
+
b.classList.remove("selected-swatch", "ring-offset-2");
|
|
1135
|
+
});
|
|
1136
|
+
const activeBtn = document.querySelector(`[data-color-swatch="${_colorSectionSwatch}"]`);
|
|
1137
|
+
if (activeBtn) activeBtn.classList.add("selected-swatch", "ring-offset-2");
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
const contentM = t.match(/^<div[^>]*>\n\n([\s\S]*?)\n\n<\/div>$/);
|
|
1141
|
+
if (contentM) document.getElementById("snip-colored-content").value = contentM[1];
|
|
1142
|
+
break;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
snippetUpdatePreview();
|
|
1146
|
+
}
|