sora 24.02.26 → 24.8.4

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,364 @@
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
+ // リストを更新
161
+ const showList = (data: any, files: string[]) => {
162
+ const contents = document.getElementById("contents")
163
+ removeChild(contents)
164
+
165
+ // テキスト一覧を追加
166
+ for (const content of data) {
167
+ const text = content.content
168
+ const uuid = content.uuid
169
+
170
+ const fluentOpt = document.createElement("fluent-option")
171
+ fluentOpt.textContent = text
172
+ fluentOpt.setAttribute("value", uuid)
173
+ fluentOpt.setAttribute("type", "text")
174
+
175
+ fluentOpt.addEventListener("click", () => {
176
+ const items = document.getElementById("items")
177
+ items.classList.add("visible")
178
+ items.classList.remove("type-file")
179
+ items.classList.add("type-text")
180
+
181
+ const textData = document.getElementById("text-title")
182
+ textData.textContent = fluentOpt.textContent
183
+
184
+ const textContent = document.getElementById("text-content")
185
+ textContent.textContent = fluentOpt.textContent
186
+ })
187
+
188
+ contents.appendChild(fluentOpt)
189
+ }
190
+
191
+ // ファイル一覧を追加
192
+ for (const text of files){
193
+ const fluentOpt = document.createElement("fluent-option")
194
+ fluentOpt.textContent = text
195
+ fluentOpt.setAttribute("type", "file")
196
+
197
+ fluentOpt.addEventListener("click", () => {
198
+ const items = document.getElementById("items")
199
+ items.classList.add("visible")
200
+ items.classList.add("type-file")
201
+ items.classList.remove("type-text")
202
+
203
+ const textData = document.getElementById("file-title")
204
+ textData.textContent = fluentOpt.textContent
205
+ })
206
+ contents.appendChild(fluentOpt)
207
+ }
208
+ }
209
+
210
+ const refresh = (visibleDetails: boolean = false) => {
211
+ // 一覧を表示
212
+ fetch('/cgi-bin?data=')
213
+ .then((response) => response.json())
214
+ .then((data) => {
215
+ // showList(data)
216
+ fetch('/cgi-bin?files')
217
+ .then((response) => response.json())
218
+ .then((files) => {
219
+ showList(data, files)
220
+ progress(false)
221
+ if(visibleDetails) {
222
+ const items = document.getElementById("items")
223
+ items.classList.remove("visible")
224
+ }
225
+ }
226
+ )
227
+ }
228
+ )
229
+ }
230
+
231
+ refresh()
232
+
233
+ // データ送信
234
+ document.getElementById("send-data").addEventListener("click", () => {
235
+ progress(true)
236
+ const text: string = (document.getElementById("input-text") as HTMLInputElement).value
237
+ fetch('/cgi-bin?data=' + encodeURI(text))
238
+ .then((response) => response.json())
239
+ .then((data) => {
240
+ // showList(data)
241
+ fetch('/cgi-bin?files')
242
+ .then((response) => response.json())
243
+ .then((files) => {
244
+ showList(data, files)
245
+ progress(false)
246
+ }
247
+ )
248
+ }
249
+ )
250
+ })
251
+
252
+ /* テキストのコピーがクリックされたとき */
253
+ const copyText = document.getElementById("copy-text") as Button
254
+ copyText.addEventListener("click", () => {
255
+ const contents = document.getElementById("contents") as Listbox
256
+ if(contents.selectedIndex != -1){
257
+ // console.log(contents)
258
+ const selected = contents.selectedOptions[0]
259
+ selected.textContent
260
+
261
+ const textarea: HTMLTextAreaElement = document.createElement("textarea")
262
+ textarea.value = selected.textContent
263
+ document.body.appendChild(textarea)
264
+ textarea.select()
265
+ document.execCommand("copy")
266
+ document.body.removeChild(textarea)
267
+ }
268
+ })
269
+
270
+ /* テキストの削除がクリックされたとき */
271
+ const removeText = document.getElementById("remove-text") as Button
272
+ removeText.addEventListener("click", () => {
273
+ progress(true)
274
+ const contents = document.getElementById("contents") as Listbox
275
+ let uuid: string
276
+ if(contents.selectedIndex != -1){
277
+ const selected = contents.selectedOptions[0]
278
+ uuid = selected.value
279
+ }else{
280
+ return
281
+ }
282
+
283
+ fetch('/cgi-bin?delete=' + uuid)
284
+ .then((response) => response.json())
285
+ .then((_data) => {
286
+ refresh(true)
287
+ }
288
+ )
289
+ })
290
+
291
+ /* ファイルの削除がクリックされたとき */
292
+ const removeFile = document.getElementById("remove-file") as Button
293
+ removeFile.addEventListener("click", () => {
294
+ progress(true)
295
+ const contents = document.getElementById("contents") as Listbox
296
+ let fileName: string
297
+ if(contents.selectedIndex != -1){
298
+ const selected = contents.selectedOptions[0]
299
+ fileName = selected.textContent
300
+ }else{
301
+ return
302
+ }
303
+
304
+ fetch('/cgi-bin?removefile=' + fileName)
305
+ .then((response) => response.json())
306
+ .then((_data) => {
307
+ refresh(true)
308
+ }
309
+ )
310
+ refresh()
311
+ })
312
+
313
+ const downloadFile = (url: string, filename: string): void => {
314
+ "use strict"
315
+
316
+ // XMLHttpRequestオブジェクトを作成する
317
+ const xhr: XMLHttpRequest = new XMLHttpRequest()
318
+ xhr.open("GET", url, true)
319
+ xhr.responseType = "blob" // Blobオブジェクトとしてダウンロードする
320
+ xhr.onload = (): void => {
321
+ // ダウンロード完了後の処理を定義する
322
+ const blob: Blob = xhr.response
323
+ // Blobオブジェクトを指すURLオブジェクトを作る
324
+ const objectURL: string = window.URL.createObjectURL(blob)
325
+ // リンク(<a>要素)を生成し、JavaScriptからクリックする
326
+ const link: HTMLAnchorElement = document.createElement("a")
327
+ document.body.appendChild(link)
328
+ link.href = objectURL
329
+ link.download = filename
330
+ link.click()
331
+ document.body.removeChild(link)
332
+ }
333
+ // XMLHttpRequestオブジェクトの通信を開始する
334
+ xhr.send()
335
+ }
336
+
337
+ /* ファイルを開く */
338
+ const openFile = document.getElementById("open-file") as Button
339
+ openFile.addEventListener("click", () => {
340
+ const contents = document.getElementById("contents") as Listbox
341
+ let fileName: string
342
+ if(contents.selectedIndex != -1){
343
+ const selected = contents.selectedOptions[0]
344
+ fileName = selected.textContent
345
+ window.open(`/get?filename=${fileName}`, '_blank')
346
+ }else{
347
+ return
348
+ }
349
+ })
350
+
351
+ /* ファイルをダウンロード */
352
+ const downloadFileE = document.getElementById("download-file") as Button
353
+ downloadFileE.addEventListener("click", () => {
354
+ const contents = document.getElementById("contents") as Listbox
355
+ let fileName: string
356
+ if(contents.selectedIndex != -1){
357
+ const selected = contents.selectedOptions[0]
358
+ fileName = selected.textContent
359
+ downloadFile(`/get?filename=${fileName}`, fileName)
360
+ }else{
361
+ return
362
+ }
363
+ })
364
+
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
+ }