sora 24.02.26 → 25.1.25

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.
data/src/index.ts ADDED
@@ -0,0 +1,374 @@
1
+ require('./style.css')
2
+ import { fluentProgress, fluentListbox, fluentOption, fluentButton, fluentCard, fluentTextField, fluentSwitch, provideFluentDesignSystem, Listbox, TextArea, Button } from '@fluentui/web-components'
3
+ import i18next from 'i18next'
4
+
5
+ provideFluentDesignSystem()
6
+ .register(
7
+ fluentProgress(),
8
+ fluentListbox(),
9
+ fluentOption(),
10
+ fluentButton(),
11
+ fluentCard(),
12
+ fluentTextField(),
13
+ fluentSwitch()
14
+ )
15
+
16
+ const setAttribute = (id: string) => {
17
+ const element = document.getElementById(id)
18
+ if(element != null) element.setAttribute("placeholder", i18next.t(id))
19
+ }
20
+
21
+ const setText = (id: string) => {
22
+ const element = document.getElementById(id)
23
+ if(element != null) element.textContent = i18next.t(id)
24
+ }
25
+
26
+ i18next.init({
27
+ lng: 'ja', // if you're using a language detector, do not define the lng option
28
+ debug: true,
29
+ resources: {
30
+ en: {
31
+ translation: {
32
+ "title-sora": "sora",
33
+ "input-text": "Input text...",
34
+ "select-file": "Select a file...",
35
+ "send-data": "Send",
36
+ "remove-file": "Remove a file",
37
+ "remove-text": "Remove a text",
38
+ "copy-text": "Copy a text",
39
+ "open-file": "Open a file",
40
+ "download-file": "Download a file"
41
+ }
42
+ },
43
+ ja: {
44
+ translation: {
45
+ "title-sora": "ソラ",
46
+ "input-text": "テキストを入力...",
47
+ "select-file": "ファイルの選択",
48
+ "send-data": "送信",
49
+ "remove-file": "ファイルの削除",
50
+ "remove-text": "テキストの削除",
51
+ "copy-text": "テキストのコピー",
52
+ "open-file": "ファイルを開く",
53
+ "download-file": "ファイルのダウンロード"
54
+ }
55
+ }
56
+ }
57
+ }, () => {
58
+ setText("title-sora")
59
+ setAttribute("input-text")
60
+ setText("select-file")
61
+ setText("send-data")
62
+ setText("remove-file")
63
+ setText("remove-text")
64
+ setText("copy-text")
65
+ setText("open-file")
66
+ setText("download-file")
67
+ })
68
+
69
+ const progress = (isOn: boolean) => {
70
+ const progressBar = document.getElementById("progress")
71
+ if(isOn)
72
+ progressBar.classList.remove("hidden-progress")
73
+ else
74
+ progressBar.classList.add("hidden-progress")
75
+ }
76
+
77
+ // 与えられたオブジェクトのキーと値をエンコードして、HTMLフォームのクエリ文字列として連結
78
+ const encodeHTMLForm = (data: Record<string, string>) => {
79
+ const params: string[] = []
80
+ for (const name in data) {
81
+ const value = data[name]
82
+ const param = encodeURIComponent(name) + '=' + encodeURIComponent(value)
83
+ params.push(param)
84
+ }
85
+ return params.join('&').replace(/%20/g, '+')
86
+ }
87
+
88
+ const getFiles = () => {
89
+ const xhr = new XMLHttpRequest()
90
+ xhr.open("POST", "/cgi-bin")
91
+ xhr.send(encodeHTMLForm({ "files": "" }))
92
+ xhr.onreadystatechange = () => {
93
+ let texts = JSON.parse(xhr.responseText || "null")
94
+ if (texts == null)
95
+ return
96
+
97
+ // const fileListUl = document.getElementById('file-list-ul')
98
+ // fileListUl.innerHTML = ""
99
+
100
+ // const fileList = document.getElementById('file-list')
101
+ // fileList.style.display = texts.length == 0 ? "none" : "block"
102
+ // fileListWrap.style.display = texts.length == 0 ? "none" : "block"
103
+
104
+ for(const fileName of texts){
105
+ const div = document.createElement('li')
106
+ div.textContent = fileName
107
+
108
+ div.addEventListener('contextmenu', (e) => {
109
+ e.preventDefault()
110
+ contextmenuFile.style.left = e.pageX + 'px'
111
+ contextmenuFile.style.top = e.pageY + 'px'
112
+ contextmenuFile.style.display = 'block'
113
+ fileTarget = e.target
114
+ })
115
+ div.addEventListener('click', () => {
116
+ contextmenuFile.style.display = 'none'
117
+ location.href = `/get?filename=${fileName}`
118
+ })
119
+
120
+ // fileListUl.append(div)
121
+ }
122
+ }
123
+ }
124
+
125
+ // ファイルアップロード
126
+ const selectFileButtonPre = document.getElementById("select-file-button-pre")
127
+ const selectFile = document.getElementById("select-file")
128
+ if(selectFile != null && selectFileButtonPre != null){
129
+ selectFile.addEventListener("click", () => selectFileButtonPre.click())
130
+ selectFileButtonPre.addEventListener("change", () => {
131
+ const form: HTMLFormElement = document.getElementById("upload-form") as HTMLFormElement
132
+ const formData: FormData = new FormData(form)
133
+
134
+ const xhr = new XMLHttpRequest()
135
+ xhr.open("POST", "/cgi-bin")
136
+ xhr.upload.addEventListener('loadstart', () => {
137
+ console.log("Upload: start")
138
+ })
139
+
140
+ xhr.upload.addEventListener('load', () => {
141
+ // アップロード正常終了
142
+ console.log('Upload: done')
143
+ getFiles()
144
+ refresh()
145
+ })
146
+
147
+ progress(true)
148
+ xhr.send(formData)
149
+ })
150
+ }
151
+
152
+ // 子要素を削除
153
+ const removeChild = (element: HTMLElement) => {
154
+ const children: HTMLCollection = element.children
155
+ for(const child of Array.from(children)){
156
+ child.remove()
157
+ }
158
+ }
159
+
160
+ const b64ToUtf8 = (str: string): string => {
161
+ return decodeURIComponent(atob(str));
162
+ }
163
+
164
+ // リストを更新
165
+ const showList = (data: any, files: string[]) => {
166
+ const contents = document.getElementById("contents")
167
+ removeChild(contents)
168
+
169
+ // テキスト一覧を追加
170
+ for (const content of data) {
171
+ const text = b64ToUtf8(content.content)
172
+ const uuid = content.uuid
173
+
174
+ const fluentOpt = document.createElement("fluent-option")
175
+ fluentOpt.textContent = text
176
+ fluentOpt.setAttribute("value", uuid)
177
+ fluentOpt.setAttribute("type", "text")
178
+
179
+ fluentOpt.addEventListener("click", () => {
180
+ const items = document.getElementById("items")
181
+ items.classList.add("visible")
182
+ items.classList.remove("type-file")
183
+ items.classList.add("type-text")
184
+
185
+ const textData = document.getElementById("text-title")
186
+ textData.textContent = fluentOpt.textContent
187
+
188
+ const textContent = document.getElementById("text-content")
189
+ textContent.textContent = fluentOpt.textContent
190
+ })
191
+
192
+ contents.appendChild(fluentOpt)
193
+ }
194
+
195
+ // ファイル一覧を追加
196
+ for (const text of files){
197
+ const fluentOpt = document.createElement("fluent-option")
198
+ fluentOpt.textContent = text
199
+ fluentOpt.setAttribute("type", "file")
200
+
201
+ fluentOpt.addEventListener("click", () => {
202
+ const items = document.getElementById("items")
203
+ items.classList.add("visible")
204
+ items.classList.add("type-file")
205
+ items.classList.remove("type-text")
206
+
207
+ const textData = document.getElementById("file-title")
208
+ textData.textContent = fluentOpt.textContent
209
+ })
210
+ contents.appendChild(fluentOpt)
211
+ }
212
+ }
213
+
214
+ const refresh = (visibleDetails: boolean = false) => {
215
+ // 一覧を表示
216
+ fetch('/cgi-bin?data=')
217
+ .then((response) => response.json())
218
+ .then((data) => {
219
+ // showList(data)
220
+ fetch('/cgi-bin?files')
221
+ .then((response) => response.json())
222
+ .then((files) => {
223
+ showList(data, files)
224
+ progress(false)
225
+ if(visibleDetails) {
226
+ const items = document.getElementById("items")
227
+ items.classList.remove("visible")
228
+ }
229
+ }
230
+ )
231
+ }
232
+ )
233
+ }
234
+
235
+ refresh()
236
+
237
+
238
+ const utf8ToB64 = (str: string): string => {
239
+ return btoa(encodeURIComponent(str))
240
+ }
241
+
242
+ // データ送信
243
+ document.getElementById("send-data").addEventListener("click", () => {
244
+ progress(true)
245
+ const text: string = (document.getElementById("input-text") as HTMLInputElement).value
246
+ const base64Encoded: string = utf8ToB64(text);
247
+ fetch('/cgi-bin?data=' + base64Encoded)
248
+ .then((response) => response.json())
249
+ .then((data) => {
250
+ // showList(data)
251
+ fetch('/cgi-bin?files')
252
+ .then((response) => response.json())
253
+ .then((files) => {
254
+ showList(data, files)
255
+ progress(false)
256
+ }
257
+ )
258
+ }
259
+ )
260
+ })
261
+
262
+ /* テキストのコピーがクリックされたとき */
263
+ const copyText = document.getElementById("copy-text") as Button
264
+ copyText.addEventListener("click", () => {
265
+ const contents = document.getElementById("contents") as Listbox
266
+ if(contents.selectedIndex != -1){
267
+ // console.log(contents)
268
+ const selected = contents.selectedOptions[0]
269
+ selected.textContent
270
+
271
+ const textarea: HTMLTextAreaElement = document.createElement("textarea")
272
+ textarea.value = selected.textContent
273
+ document.body.appendChild(textarea)
274
+ textarea.select()
275
+ document.execCommand("copy")
276
+ document.body.removeChild(textarea)
277
+ }
278
+ })
279
+
280
+ /* テキストの削除がクリックされたとき */
281
+ const removeText = document.getElementById("remove-text") as Button
282
+ removeText.addEventListener("click", () => {
283
+ progress(true)
284
+ const contents = document.getElementById("contents") as Listbox
285
+ let uuid: string
286
+ if(contents.selectedIndex != -1){
287
+ const selected = contents.selectedOptions[0]
288
+ uuid = selected.value
289
+ }else{
290
+ return
291
+ }
292
+
293
+ fetch('/cgi-bin?delete=' + uuid)
294
+ .then((response) => response.json())
295
+ .then((_data) => {
296
+ refresh(true)
297
+ }
298
+ )
299
+ })
300
+
301
+ /* ファイルの削除がクリックされたとき */
302
+ const removeFile = document.getElementById("remove-file") as Button
303
+ removeFile.addEventListener("click", () => {
304
+ progress(true)
305
+ const contents = document.getElementById("contents") as Listbox
306
+ let fileName: string
307
+ if(contents.selectedIndex != -1){
308
+ const selected = contents.selectedOptions[0]
309
+ fileName = selected.textContent
310
+ }else{
311
+ return
312
+ }
313
+
314
+ fetch('/cgi-bin?removefile=' + fileName)
315
+ .then((response) => response.json())
316
+ .then((_data) => {
317
+ refresh(true)
318
+ }
319
+ )
320
+ refresh()
321
+ })
322
+
323
+ const downloadFile = (url: string, filename: string): void => {
324
+ "use strict"
325
+
326
+ // XMLHttpRequestオブジェクトを作成する
327
+ const xhr: XMLHttpRequest = new XMLHttpRequest()
328
+ xhr.open("GET", url, true)
329
+ xhr.responseType = "blob" // Blobオブジェクトとしてダウンロードする
330
+ xhr.onload = (): void => {
331
+ // ダウンロード完了後の処理を定義する
332
+ const blob: Blob = xhr.response
333
+ // Blobオブジェクトを指すURLオブジェクトを作る
334
+ const objectURL: string = window.URL.createObjectURL(blob)
335
+ // リンク(<a>要素)を生成し、JavaScriptからクリックする
336
+ const link: HTMLAnchorElement = document.createElement("a")
337
+ document.body.appendChild(link)
338
+ link.href = objectURL
339
+ link.download = filename
340
+ link.click()
341
+ document.body.removeChild(link)
342
+ }
343
+ // XMLHttpRequestオブジェクトの通信を開始する
344
+ xhr.send()
345
+ }
346
+
347
+ /* ファイルを開く */
348
+ const openFile = document.getElementById("open-file") as Button
349
+ openFile.addEventListener("click", () => {
350
+ const contents = document.getElementById("contents") as Listbox
351
+ let fileName: string
352
+ if(contents.selectedIndex != -1){
353
+ const selected = contents.selectedOptions[0]
354
+ fileName = selected.textContent
355
+ window.open(`/get?filename=${fileName}`, '_blank')
356
+ }else{
357
+ return
358
+ }
359
+ })
360
+
361
+ /* ファイルをダウンロード */
362
+ const downloadFileE = document.getElementById("download-file") as Button
363
+ downloadFileE.addEventListener("click", () => {
364
+ const contents = document.getElementById("contents") as Listbox
365
+ let fileName: string
366
+ if(contents.selectedIndex != -1){
367
+ const selected = contents.selectedOptions[0]
368
+ fileName = selected.textContent
369
+ downloadFile(`/get?filename=${fileName}`, fileName)
370
+ }else{
371
+ return
372
+ }
373
+ })
374
+
data/src/style.css ADDED
@@ -0,0 +1,157 @@
1
+ body {
2
+ /* background-color: green; */
3
+ height: 100vh;
4
+ margin: 0;
5
+ }
6
+
7
+ main {
8
+ padding: 8px;
9
+ display: grid;
10
+ grid-template-rows: auto 1fr auto;
11
+ height: calc(100% - 19px);
12
+ gap: 8px;
13
+ }
14
+
15
+ #items {
16
+ grid-row: 2 / 3;
17
+ display: grid;
18
+ grid-template-columns: 1fr auto;
19
+ transition: 1s;
20
+ }
21
+
22
+ #items.visible {
23
+ gap: 8px;
24
+ }
25
+
26
+ #items #contents {
27
+ grid-column: 1 / 2;
28
+ overflow-x: auto;
29
+ height: calc(100vh - 66px);
30
+ }
31
+
32
+ #items #details {
33
+ padding: 0;
34
+ width: 0;
35
+ border: unset;
36
+ box-shadow: unset;
37
+ }
38
+
39
+ #items.visible #details {
40
+ padding: 8px;
41
+ width: 480px;
42
+ border: solid 1px #F3F3F3;
43
+ }
44
+
45
+ #items.type-text #details .file-details {
46
+ display: none;
47
+ }
48
+
49
+ #items.type-file #details .text-details {
50
+ display: none;
51
+ }
52
+
53
+ #items.type-file #details .file-details {
54
+ display: grid;
55
+ grid-template-rows: auto 1fr auto auto;
56
+ gap: 8px;
57
+ height: 100%;
58
+ }
59
+
60
+ #items.type-file #details .file-details #file-title {
61
+ grid-row: 1 / 2;
62
+ margin: 8px 4px 0 4px;
63
+ }
64
+
65
+ #items.type-file #details .file-details #operation-file {
66
+ grid-row: 3 / 4;
67
+ display: grid;
68
+ grid-template-columns: 1fr 1fr;
69
+ gap: 8px;
70
+ }
71
+
72
+ #items.type-file #details .file-details #remove-file {
73
+ grid-row: 4 / 5;
74
+ }
75
+
76
+ /* テキスト */
77
+ #items.type-text #details .text-details {
78
+ display: grid;
79
+ grid-template-rows: auto auto auto auto;
80
+ gap: 8px;
81
+ }
82
+
83
+ #items.type-text #details .text-details #text-title {
84
+ grid-row: 1 / 2;
85
+ white-space: nowrap;
86
+ text-overflow: ellipsis;
87
+ overflow-x: hidden;
88
+ margin: 8px 4px 0 4px;
89
+ user-select: none;
90
+ height: 36px;
91
+ }
92
+
93
+ #items.type-text #details .text-details #text-content {
94
+ grid-row: 2 / 3;
95
+ margin: 0 4px;
96
+ height: calc(100vh - 217px);
97
+ overflow-y: auto;
98
+ }
99
+
100
+ #items.type-text #details .text-details #remove-text {
101
+ grid-row: 4 / 5;
102
+ }
103
+ /* ここまで */
104
+
105
+
106
+ #input-content{
107
+ grid-row: 3 / 4;
108
+ display: grid;
109
+ grid-template-columns: 1fr auto auto;
110
+ gap: 8px;
111
+ }
112
+
113
+ #contents fluent-option {
114
+ flex-shrink: 0;
115
+ margin: 2px 4px;
116
+ }
117
+
118
+ /* 進捗バー */
119
+ #progress.hidden-progress {
120
+ visibility: hidden;
121
+ }
122
+
123
+ @media only screen and (max-width: 768px) {
124
+ main {
125
+ display: block;
126
+ }
127
+
128
+ #items {
129
+ grid-template-columns: unset;
130
+ /* grid-template-rows: auto auto; */
131
+ display: block;
132
+ }
133
+
134
+ #items #contents {
135
+ height: unset;
136
+ margin-top: 8px;
137
+ width: calc(100vw - 16px);
138
+ }
139
+
140
+ #items #details {
141
+ height: 0;
142
+ }
143
+
144
+ #items.visible #details {
145
+ width: unset;
146
+ height: auto;
147
+ margin-top: 8px;
148
+ }
149
+
150
+ #items.type-text #details .text-details #text-content {
151
+ height: unset;
152
+ }
153
+
154
+ #input-content {
155
+ margin-top: 8px;
156
+ }
157
+ }
data/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist/",
4
+ "noImplicitAny": true,
5
+ "module": "es6",
6
+ "target": "es5",
7
+ "jsx": "react",
8
+ "allowJs": true,
9
+ "moduleResolution": "node"
10
+ }
11
+ }
data/webpack.config.js ADDED
@@ -0,0 +1,25 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ entry: './src/index.ts',
5
+ module: {
6
+ rules: [
7
+ {
8
+ test: /\.tsx?$/,
9
+ use: 'ts-loader',
10
+ exclude: /node_modules/,
11
+ },
12
+ {
13
+ test: /\.css$/i,
14
+ use: ["style-loader", "css-loader"],
15
+ },
16
+ ],
17
+ },
18
+ resolve: {
19
+ extensions: ['.tsx', '.ts', '.js'],
20
+ },
21
+ output: {
22
+ filename: 'bundle.js',
23
+ path: path.resolve(__dirname, 'dist'),
24
+ },
25
+ }