@sequent-org/moodboard 1.2.0 → 1.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequent-org/moodboard",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -1,2 +1,7 @@
1
- // Единственный экспорт пакета - готовый MoodBoard с UI
1
+ // Основной экспорт пакета - готовый MoodBoard с UI
2
2
  export { MoodBoard } from './moodboard/MoodBoard.js';
3
+
4
+ // Дополнительные экспорты для работы без bundler
5
+ export { initMoodBoardNoBundler, quickInitMoodBoard, injectCriticalStyles } from './initNoBundler.js';
6
+ export { StyleLoader } from './utils/styleLoader.js';
7
+ export { EmojiLoaderNoBundler } from './utils/emojiLoaderNoBundler.js';
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Инициализация MoodBoard для использования без bundler (в Laravel и др.)
3
+ */
4
+ import { MoodBoard } from './moodboard/MoodBoard.js';
5
+ import { StyleLoader } from './utils/styleLoader.js';
6
+ import { EmojiLoaderNoBundler } from './utils/emojiLoaderNoBundler.js';
7
+
8
+ /**
9
+ * Инициализирует MoodBoard с автоматической загрузкой стилей и ресурсов
10
+ * @param {string|HTMLElement} container - контейнер для MoodBoard
11
+ * @param {Object} options - опции MoodBoard
12
+ * @param {string} basePath - базовый путь к пакету (например: '/node_modules/moodboard-futurello/')
13
+ * @returns {Promise<MoodBoard>} готовый экземпляр MoodBoard
14
+ */
15
+ export async function initMoodBoardNoBundler(container, options = {}, basePath = '') {
16
+ console.log('🚀 Инициализация MoodBoard без bundler...');
17
+
18
+ // 1. Загружаем стили
19
+ const styleLoader = new StyleLoader();
20
+ await styleLoader.loadAllStyles(basePath);
21
+
22
+ // 2. Инициализируем загрузчик эмоджи
23
+ const emojiLoader = new EmojiLoaderNoBundler();
24
+ emojiLoader.init(basePath);
25
+
26
+ // 3. Загружаем эмоджи для использования в панели
27
+ const emojiGroups = await emojiLoader.loadEmojis();
28
+
29
+ // 4. Передаем загрузчик эмоджи в опции
30
+ const enhancedOptions = {
31
+ ...options,
32
+ emojiLoader: emojiLoader,
33
+ emojiGroups: emojiGroups,
34
+ noBundler: true
35
+ };
36
+
37
+ // 5. Создаем MoodBoard
38
+ const moodboard = new MoodBoard(container, enhancedOptions);
39
+
40
+ console.log('✅ MoodBoard инициализирован без bundler');
41
+
42
+ return moodboard;
43
+ }
44
+
45
+ /**
46
+ * Инжектирует критичные стили inline для мгновенного отображения
47
+ */
48
+ export function injectCriticalStyles() {
49
+ const criticalCSS = `
50
+ /* Критичные стили для мгновенного отображения */
51
+ .moodboard-workspace {
52
+ position: relative;
53
+ width: 100%;
54
+ height: 100%;
55
+ overflow: hidden;
56
+ background: #f7fbff;
57
+ }
58
+
59
+ .moodboard-toolbar {
60
+ position: absolute;
61
+ left: 20px;
62
+ top: 50%;
63
+ transform: translateY(-50%);
64
+ z-index: 1000;
65
+ background: white;
66
+ border-radius: 12px;
67
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
68
+ padding: 8px;
69
+ display: flex;
70
+ flex-direction: column;
71
+ gap: 4px;
72
+ }
73
+
74
+ .moodboard-topbar {
75
+ position: absolute;
76
+ top: 20px;
77
+ left: 20px;
78
+ right: 20px;
79
+ z-index: 1000;
80
+ background: white;
81
+ border-radius: 12px;
82
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
83
+ padding: 8px 16px;
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 8px;
87
+ height: 44px;
88
+ }
89
+
90
+ .moodboard-html-handles {
91
+ position: absolute;
92
+ top: 0;
93
+ left: 0;
94
+ pointer-events: none;
95
+ z-index: 999;
96
+ }
97
+
98
+ /* Скрыть до полной загрузки стилей */
99
+ .moodboard-toolbar__popup,
100
+ .moodboard-properties-panel {
101
+ opacity: 0;
102
+ transition: opacity 0.3s ease;
103
+ }
104
+
105
+ .moodboard-styles-loaded .moodboard-toolbar__popup,
106
+ .moodboard-styles-loaded .moodboard-properties-panel {
107
+ opacity: 1;
108
+ }
109
+ `;
110
+
111
+ const style = document.createElement('style');
112
+ style.id = 'moodboard-critical-styles';
113
+ style.textContent = criticalCSS;
114
+ document.head.appendChild(style);
115
+ }
116
+
117
+ /**
118
+ * Простая инициализация только с критичными стилями
119
+ * Для быстрого запуска, полные стили загружаются асинхронно
120
+ */
121
+ export function quickInitMoodBoard(container, options = {}, basePath = '') {
122
+ // Инжектируем критичные стили сразу
123
+ injectCriticalStyles();
124
+
125
+ // Загружаем полные стили асинхронно
126
+ const styleLoader = new StyleLoader();
127
+ styleLoader.loadAllStyles(basePath).then(() => {
128
+ // Добавляем класс для показа полностью загруженных стилей
129
+ if (typeof container === 'string') {
130
+ container = document.querySelector(container);
131
+ }
132
+ if (container) {
133
+ container.classList.add('moodboard-styles-loaded');
134
+ }
135
+ });
136
+
137
+ // Создаем MoodBoard с fallback эмоджи
138
+ const moodboard = new MoodBoard(container, {
139
+ ...options,
140
+ noBundler: true,
141
+ skipEmojiLoader: true // Пропускаем автозагрузку эмоджи
142
+ });
143
+
144
+ return moodboard;
145
+ }
package/src/ui/Toolbar.js CHANGED
@@ -906,24 +906,33 @@ export class Toolbar {
906
906
  this.emojiPopupEl = document.createElement('div');
907
907
  this.emojiPopupEl.className = 'moodboard-toolbar__popup moodboard-toolbar__popup--emoji';
908
908
  this.emojiPopupEl.style.display = 'none';
909
- // Загружаем все изображения из папки src/assets/emodji (png/svg) через Vite import.meta.glob
910
- const modules = import.meta.glob('../assets/emodji/**/*.{png,PNG,svg,SVG}', { eager: true, as: 'url' });
911
909
 
912
- // Группируем по подпапкам внутри emodji (категории)
913
- const entries = Object.entries(modules).sort(([a], [b]) => a.localeCompare(b));
914
- const groups = new Map();
915
- entries.forEach(([path, url]) => {
916
- const marker = '/emodji/';
917
- const idx = path.indexOf(marker);
918
- let category = 'Разное';
919
- if (idx >= 0) {
920
- const after = path.slice(idx + marker.length);
921
- const parts = after.split('/');
922
- category = parts.length > 1 ? parts[0] : 'Разное';
923
- }
924
- if (!groups.has(category)) groups.set(category, []);
925
- groups.get(category).push({ path, url });
926
- });
910
+ // Определяем способ загрузки эмоджи
911
+ let groups = new Map();
912
+
913
+ if (typeof import.meta !== 'undefined' && import.meta.glob) {
914
+ // Режим с bundler (Vite) - используем import.meta.glob
915
+ const modules = import.meta.glob('../assets/emodji/**/*.{png,PNG,svg,SVG}', { eager: true, as: 'url' });
916
+
917
+ // Группируем по подпапкам внутри emodji (категории)
918
+ const entries = Object.entries(modules).sort(([a], [b]) => a.localeCompare(b));
919
+ entries.forEach(([path, url]) => {
920
+ const marker = '/emodji/';
921
+ const idx = path.indexOf(marker);
922
+ let category = 'Разное';
923
+ if (idx >= 0) {
924
+ const after = path.slice(idx + marker.length);
925
+ const parts = after.split('/');
926
+ category = parts.length > 1 ? parts[0] : 'Разное';
927
+ }
928
+ if (!groups.has(category)) groups.set(category, []);
929
+ groups.get(category).push({ path, url });
930
+ });
931
+ } else {
932
+ // Режим без bundler - используем статичный список
933
+ console.log('🎭 Toolbar: Режим без bundler, используем статичные эмоджи');
934
+ groups = this.getFallbackEmojiGroups();
935
+ }
927
936
 
928
937
  // Задаем желаемый порядок категорий
929
938
  const ORDER = ['Смайлики', 'Жесты', 'Женские эмоции', 'Котики', 'Разное'];
@@ -1019,6 +1028,75 @@ export class Toolbar {
1019
1028
  this.container.appendChild(this.emojiPopupEl);
1020
1029
  }
1021
1030
 
1031
+ /**
1032
+ * Возвращает fallback группы эмоджи для работы без bundler
1033
+ */
1034
+ getFallbackEmojiGroups() {
1035
+ const groups = new Map();
1036
+
1037
+ // Определяем базовый путь для эмоджи
1038
+ const basePath = this.getEmojiBasePath();
1039
+
1040
+ // Статичный список эмоджи с реальными именами файлов
1041
+ const fallbackEmojis = {
1042
+ 'Смайлики': [
1043
+ '1f600.png', '1f601.png', '1f602.png', '1f603.png', '1f604.png',
1044
+ '1f605.png', '1f606.png', '1f607.png', '1f609.png', '1f60a.png',
1045
+ '1f60b.png', '1f60c.png', '1f60d.png', '1f60e.png', '1f60f.png',
1046
+ '1f610.png', '1f611.png', '1f612.png', '1f613.png', '1f614.png',
1047
+ '1f615.png', '1f616.png', '1f617.png', '1f618.png', '1f619.png'
1048
+ ],
1049
+ 'Жесты': [
1050
+ '1f446.png', '1f447.png', '1f448.png', '1f449.png', '1f44a.png',
1051
+ '1f44b.png', '1f44c.png', '1f450.png', '1f4aa.png', '1f590.png',
1052
+ '1f596.png', '1f64c.png', '1f64f.png', '270c.png', '270d.png'
1053
+ ],
1054
+ 'Женские эмоции': [
1055
+ '1f645.png', '1f646.png', '1f64b.png', '1f64d.png', '1f64e.png'
1056
+ ],
1057
+ 'Котики': [
1058
+ '1f638.png', '1f639.png', '1f63a.png', '1f63b.png', '1f63c.png',
1059
+ '1f63d.png', '1f63e.png', '1f63f.png', '1f640.png'
1060
+ ],
1061
+ 'Разное': [
1062
+ '1f440.png', '1f441.png', '1f499.png', '1f4a1.png', '1f4a3.png',
1063
+ '1f4a9.png', '1f4ac.png', '1f4af.png', '2764.png', '203c.png', '26d4.png'
1064
+ ]
1065
+ };
1066
+
1067
+ Object.entries(fallbackEmojis).forEach(([category, emojis]) => {
1068
+ const emojiList = emojis.map(file => ({
1069
+ path: `${basePath}${category}/${file}`,
1070
+ url: `${basePath}${category}/${file}`
1071
+ }));
1072
+ groups.set(category, emojiList);
1073
+ });
1074
+
1075
+ return groups;
1076
+ }
1077
+
1078
+ /**
1079
+ * Определяет базовый путь для эмоджи в зависимости от режима
1080
+ */
1081
+ getEmojiBasePath() {
1082
+ // Проверяем, есть ли глобальная настройка базового пути
1083
+ if (window.MOODBOARD_BASE_PATH) {
1084
+ return `${window.MOODBOARD_BASE_PATH}src/assets/emodji/`;
1085
+ }
1086
+
1087
+ // Попытка определить автоматически
1088
+ const scripts = document.querySelectorAll('script[src]');
1089
+ for (const script of scripts) {
1090
+ if (script.src.includes('moodboard') || script.src.includes('node_modules')) {
1091
+ const baseUrl = new URL(script.src).origin;
1092
+ return `${baseUrl}/node_modules/moodboard-futurello/src/assets/emodji/`;
1093
+ }
1094
+ }
1095
+
1096
+ // Fallback: относительный путь
1097
+ return './src/assets/emodji/';
1098
+ }
1099
+
1022
1100
  toggleEmojiPopup(anchorButton) {
1023
1101
  if (!this.emojiPopupEl) return;
1024
1102
  if (this.emojiPopupEl.style.display === 'none') {
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Загрузчик эмоджи для работы без bundler
3
+ * Заменяет import.meta.glob на динамическую загрузку
4
+ */
5
+ export class EmojiLoaderNoBundler {
6
+ constructor() {
7
+ this.emojiCache = new Map();
8
+ this.basePath = '';
9
+ }
10
+
11
+ /**
12
+ * Инициализация с базовым путем
13
+ * @param {string} basePath - путь к папке с эмоджи (например: '/node_modules/moodboard-futurello/')
14
+ */
15
+ init(basePath = '') {
16
+ this.basePath = basePath;
17
+ }
18
+
19
+ /**
20
+ * Загружает список эмоджи из статичного индекса
21
+ * @returns {Promise<Map>} карта категорий и эмоджи
22
+ */
23
+ async loadEmojis() {
24
+ try {
25
+ // Попытка загрузить индекс эмоджи из JSON файла
26
+ const response = await fetch(`${this.basePath}src/assets/emodji/index.json`);
27
+ if (response.ok) {
28
+ const emojiIndex = await response.json();
29
+ return this.processEmojiIndex(emojiIndex);
30
+ }
31
+ } catch (error) {
32
+ console.warn('⚠️ Не удалось загрузить индекс эмоджи, используем fallback');
33
+ }
34
+
35
+ // Fallback: используем статичный список популярных эмоджи
36
+ return this.getFallbackEmojis();
37
+ }
38
+
39
+ /**
40
+ * Обрабатывает индекс эмоджи
41
+ * @param {Object} emojiIndex - индекс эмоджи из JSON
42
+ */
43
+ processEmojiIndex(emojiIndex) {
44
+ const groups = new Map();
45
+
46
+ Object.entries(emojiIndex).forEach(([category, emojis]) => {
47
+ const emojiList = emojis.map(emoji => ({
48
+ path: `${this.basePath}src/assets/emodji/${category}/${emoji.file}`,
49
+ url: `${this.basePath}src/assets/emodji/${category}/${emoji.file}`
50
+ }));
51
+ groups.set(category, emojiList);
52
+ });
53
+
54
+ return groups;
55
+ }
56
+
57
+ /**
58
+ * Возвращает fallback список эмоджи
59
+ */
60
+ getFallbackEmojis() {
61
+ const groups = new Map();
62
+
63
+ // Статичный список популярных эмоджи
64
+ const fallbackEmojis = {
65
+ 'Смайлики': [
66
+ '1f600.png', '1f601.png', '1f602.png', '1f603.png', '1f604.png',
67
+ '1f605.png', '1f606.png', '1f607.png', '1f608.png', '1f609.png',
68
+ '1f60a.png', '1f60b.png', '1f60c.png', '1f60d.png', '1f60e.png',
69
+ '1f60f.png', '1f610.png', '1f611.png', '1f612.png', '1f613.png',
70
+ '1f614.png', '1f615.png', '1f616.png', '1f617.png', '1f618.png',
71
+ '1f619.png', '1f61a.png', '1f61b.png', '1f61c.png', '1f61d.png',
72
+ '1f61e.png', '1f61f.png', '1f620.png', '1f621.png', '1f622.png'
73
+ ],
74
+ 'Жесты': [
75
+ '1f44d.png', '1f44e.png', '1f44f.png', '1f450.png', '1f451.png',
76
+ '1f590.png', '270c.png', '1f91d.png', '1f64f.png', '1f44c.png'
77
+ ],
78
+ 'Разное': [
79
+ '2764.png', '1f494.png', '1f49c.png', '1f49a.png', '1f495.png',
80
+ '1f496.png', '1f497.png', '1f498.png', '1f499.png', '1f49b.png'
81
+ ]
82
+ };
83
+
84
+ Object.entries(fallbackEmojis).forEach(([category, emojis]) => {
85
+ const emojiList = emojis.map(file => ({
86
+ path: `${this.basePath}src/assets/emodji/${category}/${file}`,
87
+ url: `${this.basePath}src/assets/emodji/${category}/${file}`
88
+ }));
89
+ groups.set(category, emojiList);
90
+ });
91
+
92
+ return groups;
93
+ }
94
+
95
+ /**
96
+ * Создает индексный JSON файл из существующих эмоджи
97
+ * Эта функция должна быть вызвана в dev режиме для генерации индекса
98
+ */
99
+ async generateEmojiIndex() {
100
+ // Эта функция может быть вызвана только в dev среде с bundler
101
+ if (typeof import.meta !== 'undefined' && import.meta.glob) {
102
+ const modules = import.meta.glob('../assets/emodji/**/*.{png,PNG}', { eager: true, as: 'url' });
103
+ const index = {};
104
+
105
+ Object.keys(modules).forEach(path => {
106
+ const match = path.match(/\/emodji\/([^\/]+)\/([^\/]+)$/);
107
+ if (match) {
108
+ const [, category, file] = match;
109
+ if (!index[category]) index[category] = [];
110
+ index[category].push({ file });
111
+ }
112
+ });
113
+
114
+ console.log('📁 Индекс эмоджи:', JSON.stringify(index, null, 2));
115
+ return index;
116
+ }
117
+
118
+ return null;
119
+ }
120
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Утилита для загрузки CSS стилей без bundler
3
+ * Подключает все необходимые стили для MoodBoard
4
+ */
5
+ export class StyleLoader {
6
+ constructor() {
7
+ this.loadedStyles = new Set();
8
+ }
9
+
10
+ /**
11
+ * Загружает все стили MoodBoard
12
+ * @param {string} basePath - базовый путь к node_modules или dist
13
+ */
14
+ async loadAllStyles(basePath = '') {
15
+ const styles = [
16
+ 'src/ui/styles/workspace.css',
17
+ 'src/ui/styles/toolbar.css',
18
+ 'src/ui/styles/topbar.css',
19
+ 'src/ui/styles/panels.css'
20
+ ];
21
+
22
+ console.log('🎨 StyleLoader: Загружаем стили MoodBoard...');
23
+
24
+ for (const stylePath of styles) {
25
+ try {
26
+ await this.loadStyle(basePath + stylePath);
27
+ console.log(`✅ Стиль загружен: ${stylePath}`);
28
+ } catch (error) {
29
+ console.warn(`⚠️ Ошибка загрузки стиля ${stylePath}:`, error);
30
+ }
31
+ }
32
+
33
+ console.log('🎨 StyleLoader: Все стили загружены');
34
+ }
35
+
36
+ /**
37
+ * Загружает отдельный CSS файл
38
+ * @param {string} href - путь к CSS файлу
39
+ */
40
+ async loadStyle(href) {
41
+ // Проверяем, не загружен ли уже этот стиль
42
+ if (this.loadedStyles.has(href)) {
43
+ return;
44
+ }
45
+
46
+ return new Promise((resolve, reject) => {
47
+ // Создаем link элемент
48
+ const link = document.createElement('link');
49
+ link.rel = 'stylesheet';
50
+ link.type = 'text/css';
51
+ link.href = href;
52
+
53
+ // Обработчики загрузки
54
+ link.onload = () => {
55
+ this.loadedStyles.add(href);
56
+ resolve();
57
+ };
58
+
59
+ link.onerror = () => {
60
+ reject(new Error(`Failed to load CSS: ${href}`));
61
+ };
62
+
63
+ // Добавляем в head
64
+ document.head.appendChild(link);
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Загружает стили синхронно (для критичных стилей)
70
+ * @param {string} css - CSS код
71
+ * @param {string} id - уникальный ID для style элемента
72
+ */
73
+ injectInlineStyles(css, id = 'moodboard-styles') {
74
+ // Проверяем, не загружен ли уже
75
+ if (document.getElementById(id)) {
76
+ return;
77
+ }
78
+
79
+ const style = document.createElement('style');
80
+ style.id = id;
81
+ style.textContent = css;
82
+ document.head.appendChild(style);
83
+ }
84
+ }