hyperbook 0.83.0 → 0.84.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/dist/assets/client.js +24 -2
- package/dist/assets/cloud.js +752 -0
- package/dist/assets/shell.css +193 -0
- package/dist/assets/store.js +154 -23
- package/dist/index.js +264 -3
- package/dist/locales/de.json +21 -1
- package/dist/locales/en.json +21 -1
- package/package.json +4 -4
package/dist/assets/shell.css
CHANGED
|
@@ -935,3 +935,196 @@ nav.toc li.level-3 {
|
|
|
935
935
|
.main-grid.layout-standalone .breadcrumb {
|
|
936
936
|
display: none;
|
|
937
937
|
}
|
|
938
|
+
|
|
939
|
+
/* User Authentication UI */
|
|
940
|
+
#user-toggle {
|
|
941
|
+
margin-right: 8px;
|
|
942
|
+
line-height: 1;
|
|
943
|
+
background: none;
|
|
944
|
+
border: none;
|
|
945
|
+
cursor: pointer;
|
|
946
|
+
padding: 8px;
|
|
947
|
+
display: flex;
|
|
948
|
+
align-items: center;
|
|
949
|
+
justify-content: center;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
.user-icon {
|
|
953
|
+
width: 24px;
|
|
954
|
+
height: 24px;
|
|
955
|
+
background-color: var(--color-brand-text);
|
|
956
|
+
mask-size: contain;
|
|
957
|
+
mask-repeat: no-repeat;
|
|
958
|
+
mask-position: center;
|
|
959
|
+
/* not-logged-in: person with question mark */
|
|
960
|
+
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>');
|
|
961
|
+
opacity: 0.5;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/* logged-in: solid person */
|
|
965
|
+
.user-icon[data-state="logged-in"] {
|
|
966
|
+
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>');
|
|
967
|
+
opacity: 1;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/* unsynced: person with exclamation */
|
|
971
|
+
.user-icon[data-state="unsynced"] {
|
|
972
|
+
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>');
|
|
973
|
+
opacity: 1;
|
|
974
|
+
background-color: #f59e0b;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/* syncing: person with rotating animation */
|
|
978
|
+
.user-icon[data-state="syncing"] {
|
|
979
|
+
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>');
|
|
980
|
+
opacity: 1;
|
|
981
|
+
animation: user-icon-pulse 1s ease-in-out infinite;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/* synced: person with checkmark feel */
|
|
985
|
+
.user-icon[data-state="synced"] {
|
|
986
|
+
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>');
|
|
987
|
+
opacity: 1;
|
|
988
|
+
background-color: #22c55e;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
@keyframes user-icon-pulse {
|
|
992
|
+
0%, 100% { opacity: 1; }
|
|
993
|
+
50% { opacity: 0.4; }
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
#user-drawer {
|
|
997
|
+
background: var(--color-background);
|
|
998
|
+
border-left: 1px solid var(--color-nav-border);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.user-drawer-content {
|
|
1002
|
+
padding: 20px;
|
|
1003
|
+
width: 100%;
|
|
1004
|
+
max-width: 400px;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.user-form,
|
|
1008
|
+
.user-info {
|
|
1009
|
+
display: flex;
|
|
1010
|
+
flex-direction: column;
|
|
1011
|
+
gap: 15px;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
.user-form h3,
|
|
1015
|
+
.user-info h3 {
|
|
1016
|
+
margin: 0 0 10px 0;
|
|
1017
|
+
color: var(--color-text);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.user-form input {
|
|
1021
|
+
padding: 10px;
|
|
1022
|
+
border: 1px solid var(--color-nav-border);
|
|
1023
|
+
border-radius: 4px;
|
|
1024
|
+
background: var(--color-background);
|
|
1025
|
+
color: var(--color-text);
|
|
1026
|
+
font-size: 14px;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.user-form button,
|
|
1030
|
+
.user-info button {
|
|
1031
|
+
padding: 10px 20px;
|
|
1032
|
+
border: none;
|
|
1033
|
+
border-radius: 4px;
|
|
1034
|
+
cursor: pointer;
|
|
1035
|
+
font-size: 14px;
|
|
1036
|
+
background: var(--color-brand);
|
|
1037
|
+
color: var(--color-brand-text);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
.user-form button:hover,
|
|
1041
|
+
.user-info button:hover {
|
|
1042
|
+
opacity: 0.9;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
.user-info .logout-btn {
|
|
1046
|
+
background: #e74c3c;
|
|
1047
|
+
color: white;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
.user-info .logout-btn:hover {
|
|
1051
|
+
background: #c0392b;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
.user-error {
|
|
1055
|
+
color: #e74c3c;
|
|
1056
|
+
font-size: 14px;
|
|
1057
|
+
min-height: 20px;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.user-info p {
|
|
1061
|
+
margin: 5px 0;
|
|
1062
|
+
color: var(--color-text);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
#user-save-status {
|
|
1066
|
+
font-weight: normal;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
#user-save-status.unsaved {
|
|
1070
|
+
color: #f39c12;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
#user-save-status.saving {
|
|
1074
|
+
color: #f39c12;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
#user-save-status.saved {
|
|
1078
|
+
color: #27ae60;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
#user-save-status.error {
|
|
1082
|
+
color: #e74c3c;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
#user-save-status.offline {
|
|
1086
|
+
color: #95a5a6;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
#user-save-status.offline-queued {
|
|
1090
|
+
color: #f39c12;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
#user-save-status.readonly {
|
|
1094
|
+
color: #e67e22;
|
|
1095
|
+
font-weight: bold;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
#impersonation-banner {
|
|
1099
|
+
position: fixed;
|
|
1100
|
+
top: 0;
|
|
1101
|
+
left: 0;
|
|
1102
|
+
right: 0;
|
|
1103
|
+
z-index: 10000;
|
|
1104
|
+
background: #e67e22;
|
|
1105
|
+
color: white;
|
|
1106
|
+
padding: 8px 16px;
|
|
1107
|
+
display: flex;
|
|
1108
|
+
justify-content: center;
|
|
1109
|
+
align-items: center;
|
|
1110
|
+
gap: 16px;
|
|
1111
|
+
font-size: 14px;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
#impersonation-banner a {
|
|
1115
|
+
color: white;
|
|
1116
|
+
font-weight: bold;
|
|
1117
|
+
text-decoration: underline;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
#impersonation-banner a:hover {
|
|
1121
|
+
opacity: 0.8;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
#impersonation-banner ~ * {
|
|
1125
|
+
margin-top: 36px;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
.hidden {
|
|
1129
|
+
display: none !important;
|
|
1130
|
+
}
|
package/dist/assets/store.js
CHANGED
|
@@ -9,7 +9,7 @@ store.version(2).stores({
|
|
|
9
9
|
mouseX,
|
|
10
10
|
mouseY,
|
|
11
11
|
scrollX,
|
|
12
|
-
|
|
12
|
+
scrollY,
|
|
13
13
|
windowWidth,
|
|
14
14
|
windowHeight
|
|
15
15
|
`,
|
|
@@ -32,14 +32,141 @@ store.version(2).stores({
|
|
|
32
32
|
multievent: `id,state`,
|
|
33
33
|
typst: `id,code`,
|
|
34
34
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read all data from an external IndexedDB database using the raw API.
|
|
38
|
+
* Returns a Dexie-export-compatible object, or null if the DB doesn't exist.
|
|
39
|
+
*/
|
|
40
|
+
function exportExternalDB(dbName) {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const request = indexedDB.open(dbName);
|
|
43
|
+
request.onerror = () => resolve(null);
|
|
44
|
+
request.onsuccess = (event) => {
|
|
45
|
+
const db = event.target.result;
|
|
46
|
+
const storeNames = Array.from(db.objectStoreNames);
|
|
47
|
+
if (storeNames.length === 0) {
|
|
48
|
+
db.close();
|
|
49
|
+
resolve(null);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const result = {
|
|
53
|
+
formatName: "dexie",
|
|
54
|
+
formatVersion: 1,
|
|
55
|
+
data: {
|
|
56
|
+
databaseName: dbName,
|
|
57
|
+
databaseVersion: db.version,
|
|
58
|
+
tables: [],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const tx = db.transaction(storeNames, "readonly");
|
|
62
|
+
let pending = storeNames.length;
|
|
63
|
+
storeNames.forEach((name) => {
|
|
64
|
+
const objectStore = tx.objectStore(name);
|
|
65
|
+
const tableInfo = {
|
|
66
|
+
name: name,
|
|
67
|
+
schema: objectStore.keyPath ? `${objectStore.keyPath}` : "++id",
|
|
68
|
+
rowCount: 0,
|
|
69
|
+
rows: [],
|
|
70
|
+
};
|
|
71
|
+
const cursorReq = objectStore.openCursor();
|
|
72
|
+
cursorReq.onsuccess = (e) => {
|
|
73
|
+
const cursor = e.target.result;
|
|
74
|
+
if (cursor) {
|
|
75
|
+
tableInfo.rows.push(cursor.value);
|
|
76
|
+
cursor.continue();
|
|
77
|
+
} else {
|
|
78
|
+
tableInfo.rowCount = tableInfo.rows.length;
|
|
79
|
+
result.data.tables.push(tableInfo);
|
|
80
|
+
pending--;
|
|
81
|
+
if (pending === 0) {
|
|
82
|
+
db.close();
|
|
83
|
+
resolve(result);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
cursorReq.onerror = () => {
|
|
88
|
+
pending--;
|
|
89
|
+
if (pending === 0) {
|
|
90
|
+
db.close();
|
|
91
|
+
resolve(result);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Import data into an external IndexedDB database using the raw API.
|
|
101
|
+
* Accepts a Dexie-export-compatible object. Clears existing data before importing.
|
|
102
|
+
*/
|
|
103
|
+
function importExternalDB(dbName, exportData) {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const tables = exportData?.data?.tables;
|
|
106
|
+
if (!tables || tables.length === 0) { resolve(); return; }
|
|
107
|
+
|
|
108
|
+
// Determine the version the external tool uses (keep it in sync)
|
|
109
|
+
const request = indexedDB.open(dbName);
|
|
110
|
+
request.onerror = () => reject(request.error);
|
|
111
|
+
|
|
112
|
+
request.onupgradeneeded = (event) => {
|
|
113
|
+
// DB didn't exist yet — create the object stores from the export data
|
|
114
|
+
const db = event.target.result;
|
|
115
|
+
tables.forEach((table) => {
|
|
116
|
+
if (!db.objectStoreNames.contains(table.name)) {
|
|
117
|
+
const keyPath = table.schema && !table.schema.startsWith('++')
|
|
118
|
+
? table.schema.split(',')[0].trim()
|
|
119
|
+
: null;
|
|
120
|
+
db.createObjectStore(table.name, keyPath ? { keyPath } : { autoIncrement: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
request.onsuccess = (event) => {
|
|
126
|
+
const db = event.target.result;
|
|
127
|
+
const storeNames = tables
|
|
128
|
+
.map((t) => t.name)
|
|
129
|
+
.filter((n) => db.objectStoreNames.contains(n));
|
|
130
|
+
if (storeNames.length === 0) { db.close(); resolve(); return; }
|
|
131
|
+
|
|
132
|
+
const tx = db.transaction(storeNames, "readwrite");
|
|
133
|
+
// Clear then re-populate each store
|
|
134
|
+
storeNames.forEach((name) => {
|
|
135
|
+
const objectStore = tx.objectStore(name);
|
|
136
|
+
objectStore.clear();
|
|
137
|
+
const table = tables.find((t) => t.name === name);
|
|
138
|
+
if (table && table.rows) {
|
|
139
|
+
table.rows.forEach((row) => objectStore.put(row));
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
tx.oncomplete = () => { db.close(); resolve(); };
|
|
143
|
+
tx.onerror = () => { db.close(); reject(tx.error); };
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Clear all tables in an external IndexedDB database using the raw API.
|
|
150
|
+
*/
|
|
151
|
+
function clearExternalDB(dbName) {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
const request = indexedDB.open(dbName);
|
|
154
|
+
request.onerror = () => resolve();
|
|
155
|
+
request.onsuccess = (event) => {
|
|
156
|
+
const db = event.target.result;
|
|
157
|
+
const storeNames = Array.from(db.objectStoreNames);
|
|
158
|
+
if (storeNames.length === 0) {
|
|
159
|
+
db.close();
|
|
160
|
+
resolve();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const tx = db.transaction(storeNames, "readwrite");
|
|
164
|
+
storeNames.forEach((name) => tx.objectStore(name).clear());
|
|
165
|
+
tx.oncomplete = () => { db.close(); resolve(); };
|
|
166
|
+
tx.onerror = () => { db.close(); resolve(); };
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
}
|
|
43
170
|
|
|
44
171
|
const initStore = async () => {
|
|
45
172
|
store.currentState.put({
|
|
@@ -67,21 +194,27 @@ const initStore = async () => {
|
|
|
67
194
|
windowHeight: window.innerHeight,
|
|
68
195
|
});
|
|
69
196
|
});
|
|
197
|
+
|
|
198
|
+
// Initialize cloud integration if configured
|
|
199
|
+
if (window.hyperbook.cloud) {
|
|
200
|
+
await window.hyperbook.cloud.initializeStore(store);
|
|
201
|
+
}
|
|
70
202
|
};
|
|
203
|
+
|
|
71
204
|
initStore();
|
|
72
205
|
|
|
73
206
|
async function hyperbookExport() {
|
|
74
207
|
const hyperbook = await store.export({ prettyJson: true });
|
|
75
|
-
const sqlIde = await
|
|
76
|
-
const learnJ = await
|
|
208
|
+
const sqlIde = await exportExternalDB('SQL-IDE');
|
|
209
|
+
const learnJ = await exportExternalDB('LearnJ');
|
|
77
210
|
|
|
78
211
|
const data = {
|
|
79
212
|
version: 1,
|
|
80
213
|
origin: window.location.origin,
|
|
81
214
|
data: {
|
|
82
215
|
hyperbook: JSON.parse(await hyperbook.text()),
|
|
83
|
-
sqlIde:
|
|
84
|
-
learnJ:
|
|
216
|
+
sqlIde: sqlIde || {},
|
|
217
|
+
learnJ: learnJ || {},
|
|
85
218
|
},
|
|
86
219
|
};
|
|
87
220
|
|
|
@@ -108,8 +241,8 @@ async function hyperbookReset() {
|
|
|
108
241
|
}
|
|
109
242
|
|
|
110
243
|
clearTable(store);
|
|
111
|
-
|
|
112
|
-
|
|
244
|
+
await clearExternalDB('LearnJ');
|
|
245
|
+
await clearExternalDB('SQL-IDE');
|
|
113
246
|
|
|
114
247
|
alert(i18n.get("store-reset-sucessful"));
|
|
115
248
|
window.location.reload();
|
|
@@ -146,16 +279,14 @@ async function hyperbookImport() {
|
|
|
146
279
|
const hyperbookBlob = new Blob([JSON.stringify(hyperbook)], {
|
|
147
280
|
type: "application/json",
|
|
148
281
|
});
|
|
149
|
-
const sqlIdeBlob = new Blob([JSON.stringify(sqlIde)], {
|
|
150
|
-
type: "application/json",
|
|
151
|
-
});
|
|
152
|
-
const learnJBlob = new Blob([JSON.stringify(learnJ)], {
|
|
153
|
-
type: "application/json",
|
|
154
|
-
});
|
|
155
282
|
|
|
156
283
|
await store.import(hyperbookBlob, { clearTablesBeforeImport: true });
|
|
157
|
-
|
|
158
|
-
|
|
284
|
+
if (sqlIde) {
|
|
285
|
+
await importExternalDB('SQL-IDE', sqlIde);
|
|
286
|
+
}
|
|
287
|
+
if (learnJ) {
|
|
288
|
+
await importExternalDB('LearnJ', learnJ);
|
|
289
|
+
}
|
|
159
290
|
|
|
160
291
|
alert(i18n.get("store-import-sucessful"));
|
|
161
292
|
window.location.reload();
|