hyperbook 0.83.0 → 0.84.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/dist/assets/client.js +2 -2
- package/dist/assets/cloud.js +759 -0
- package/dist/assets/shell.css +194 -0
- package/dist/assets/store.js +149 -23
- package/dist/index.js +264 -3
- package/dist/locales/de.json +21 -1
- package/dist/locales/en.json +21 -1
- package/package.json +5 -5
package/dist/assets/shell.css
CHANGED
|
@@ -935,3 +935,197 @@ 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
|
+
font-family: hyperbook-body, sans-serif;
|
|
1100
|
+
position: fixed;
|
|
1101
|
+
top: 0;
|
|
1102
|
+
left: 0;
|
|
1103
|
+
right: 0;
|
|
1104
|
+
z-index: 10000;
|
|
1105
|
+
background: #e67e22;
|
|
1106
|
+
color: white;
|
|
1107
|
+
padding: 8px 16px;
|
|
1108
|
+
display: flex;
|
|
1109
|
+
justify-content: center;
|
|
1110
|
+
align-items: center;
|
|
1111
|
+
gap: 16px;
|
|
1112
|
+
font-size: 1rem;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
#impersonation-banner a {
|
|
1116
|
+
color: white;
|
|
1117
|
+
font-weight: bold;
|
|
1118
|
+
text-decoration: underline;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
#impersonation-banner a:hover {
|
|
1122
|
+
opacity: 0.8;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
#impersonation-banner ~ * {
|
|
1126
|
+
margin-top: 36px;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.hidden {
|
|
1130
|
+
display: none !important;
|
|
1131
|
+
}
|
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({
|
|
@@ -68,20 +195,21 @@ const initStore = async () => {
|
|
|
68
195
|
});
|
|
69
196
|
});
|
|
70
197
|
};
|
|
198
|
+
|
|
71
199
|
initStore();
|
|
72
200
|
|
|
73
201
|
async function hyperbookExport() {
|
|
74
202
|
const hyperbook = await store.export({ prettyJson: true });
|
|
75
|
-
const sqlIde = await
|
|
76
|
-
const learnJ = await
|
|
203
|
+
const sqlIde = await exportExternalDB('SQL-IDE');
|
|
204
|
+
const learnJ = await exportExternalDB('LearnJ');
|
|
77
205
|
|
|
78
206
|
const data = {
|
|
79
207
|
version: 1,
|
|
80
208
|
origin: window.location.origin,
|
|
81
209
|
data: {
|
|
82
210
|
hyperbook: JSON.parse(await hyperbook.text()),
|
|
83
|
-
sqlIde:
|
|
84
|
-
learnJ:
|
|
211
|
+
sqlIde: sqlIde || {},
|
|
212
|
+
learnJ: learnJ || {},
|
|
85
213
|
},
|
|
86
214
|
};
|
|
87
215
|
|
|
@@ -108,8 +236,8 @@ async function hyperbookReset() {
|
|
|
108
236
|
}
|
|
109
237
|
|
|
110
238
|
clearTable(store);
|
|
111
|
-
|
|
112
|
-
|
|
239
|
+
await clearExternalDB('LearnJ');
|
|
240
|
+
await clearExternalDB('SQL-IDE');
|
|
113
241
|
|
|
114
242
|
alert(i18n.get("store-reset-sucessful"));
|
|
115
243
|
window.location.reload();
|
|
@@ -146,16 +274,14 @@ async function hyperbookImport() {
|
|
|
146
274
|
const hyperbookBlob = new Blob([JSON.stringify(hyperbook)], {
|
|
147
275
|
type: "application/json",
|
|
148
276
|
});
|
|
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
277
|
|
|
156
278
|
await store.import(hyperbookBlob, { clearTablesBeforeImport: true });
|
|
157
|
-
|
|
158
|
-
|
|
279
|
+
if (sqlIde) {
|
|
280
|
+
await importExternalDB('SQL-IDE', sqlIde);
|
|
281
|
+
}
|
|
282
|
+
if (learnJ) {
|
|
283
|
+
await importExternalDB('LearnJ', learnJ);
|
|
284
|
+
}
|
|
159
285
|
|
|
160
286
|
alert(i18n.get("store-import-sucessful"));
|
|
161
287
|
window.location.reload();
|