├── 404.html ├── DBConstuction.txt ├── Dialog.css ├── Dialog.js ├── Error ├── 401 │ └── index.html ├── 406 │ └── index.html ├── 403.10 │ └── index.html ├── Back.jpg ├── Main.css ├── Main.js └── favicon.ico ├── LICENSE ├── Main.css ├── Main.js ├── Multi Replacer.cfg ├── Profile ├── Profile.css ├── Profile.js └── index.html ├── README.md ├── ServerInfo ├── ServerInfo.css ├── ServerInfo.js └── index.html ├── Terminal.js ├── Thread ├── Thread.css ├── Thread.js ├── Viewer │ ├── Auth │ │ ├── Auth.css │ │ ├── Auth.js │ │ └── index.html │ ├── Viewer.css │ ├── Viewer.js │ └── index.html └── index.html ├── Top ├── Top.css ├── Top.js └── index.html ├── assets ├── classes │ ├── DBLoader.js │ ├── Encrypter.js │ ├── FileLoader.js │ ├── JSONLoader.js │ └── LangLoader.js ├── firebase.json ├── images │ ├── Back.jpg │ ├── Icon-Info.png │ └── Logo.png ├── includes │ ├── Component.html │ ├── Component.js │ ├── Core.css │ ├── Core.js │ ├── dialog-polyfill.css │ └── dialog-polyfill.js └── locales │ ├── en_US.json │ └── ja_JP.json ├── favicon.ico ├── favicon.png └── index.html /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 404 Not Found 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 404 Not Found 20 |
21 | 22 |
23 |

24 | お探しのページが見つかりませんでした。 25 |

26 | 27 | 5秒後にメインページへ移動します… 28 |
29 | 30 | -------------------------------------------------------------------------------- /DBConstuction.txt: -------------------------------------------------------------------------------- 1 | Root 2 | > threads 3 | > 0: "!SYSTEM" 4 | 5 | > 1 6 | > title: String 7 | > overview: String 8 | > detail: String 9 | > jobs 10 | > Owner: Object //UIDをキーとして格納 11 | > xxxxxxxx: "" 12 | > ... 13 | 14 | > createdAt: DateString 15 | 16 | > data 17 | > 0 18 | > uid: "!SYSTEM" 19 | > content: $self.title 20 | > createdAt: $self.createdAt 21 | 22 | > 1 23 | > uid: UID 24 | > content: String 25 | > createdAt: DateString 26 | 27 | > ... 28 | 29 | > password: "" || HashString 30 | 31 | > ... 32 | 33 | > users 34 | > !SYSTEM: "" 35 | 36 | > !SYSTEM_INFO 37 | > gplusName: "" 38 | > gplusPhoto: "" 39 | > userName: "" 40 | > detail: "" 41 | 42 | > xxxxxxxx 43 | > gplusName: String 44 | > gplusPhoto: URLString 45 | > userName: String 46 | > detail: String 47 | > links: Array 48 | > [0] 49 | > name: String 50 | > url: URLString 51 | 52 | > ... 53 | 54 | > ... -------------------------------------------------------------------------------- /Dialog.css: -------------------------------------------------------------------------------- 1 | Dialog[ID^="Dialogs"] { 2 | Width: 65%; 3 | } 4 | 5 | Dialog[ID^="Dialogs"] Div.mdl-textfield { 6 | Width: 100%; 7 | } 8 | 9 | Dialog[ID^="Dialogs"] Button.mdl-button--disabled { 10 | Pointer-Events: None; 11 | } 12 | 13 | *.mdl-switch__child-hide { 14 | Display: None; 15 | } 16 | 17 | 18 | 19 | #Dialogs_Profile_InfoViewer_Content { 20 | Display: Flex; 21 | Flex-Direction: Row; 22 | Align-Items: Flex-Start; 23 | 24 | Font-Size: Medium; 25 | } 26 | 27 | #Dialogs_Profile_InfoViewer_Content_Photo { 28 | Width: 20%; 29 | 30 | Margin-Right: 5%; 31 | Border-Radius: 100%; 32 | } 33 | 34 | #Dialogs_Profile_InfoViewer_Content_Photo::Before { 35 | Content: ""; 36 | 37 | Display: Block; 38 | Padding-Top: 100%; 39 | } 40 | 41 | #Dialogs_Profile_InfoViewer_Content_Info { 42 | Flex: 1; 43 | } 44 | 45 | #Dialogs_Profile_InfoViewer_Content_Info_Detail { 46 | Padding: 1em 0 4em 0; 47 | 48 | White-Space: Pre-Wrap; 49 | } 50 | 51 | #Dialogs_Profile_InfoViewer_Content_Info_Links { 52 | Padding: 1em 0 0; 53 | } 54 | 55 | Img[Data-Component="Dialogs_Profile_InfoViewer_Content_Info_Links_Link_Icon"] { 56 | Width: 1em; 57 | } 58 | 59 | 60 | 61 | #Dialogs_Thread_InfoInputter_Btns > Button[Disabled] { 62 | Display: None; 63 | } 64 | 65 | 66 | 67 | #Dialogs_Thread_InfoViewer_Content_Overview { 68 | Padding: 1em 0 4em 0; 69 | } 70 | 71 | #Dialogs_Thread_InfoViewer_Content_Detail { 72 | Padding: 1em 0 0; 73 | 74 | White-Space: Pre-Wrap; 75 | } 76 | 77 | 78 | 79 | #Dialogs_Thread_Poster_Header { 80 | Display: Flex; 81 | 82 | Padding-Bottom: 0; 83 | } 84 | 85 | #Dialogs_Thread_Poster_Header > * { 86 | Display: Flex; 87 | Flex-Direction: Row; 88 | Align-Items: Center; 89 | 90 | Font-Size: Medium; 91 | } 92 | 93 | #Dialogs_Thread_Poster_Header > Div.mdl-menu__container { 94 | Width: 1000px; 95 | } 96 | 97 | #Dialogs_Thread_Poster_Header_Actor { 98 | Align-Self: Auto; 99 | 100 | Margin-Left: 0.5em; 101 | } 102 | 103 | #Dialogs_Thread_Poster_Menu > Li > * { 104 | Vertical-Align: Middle; 105 | } 106 | 107 | #Dialogs_Thread_Poster_Content { 108 | Padding-Top: 0; 109 | } 110 | 111 | #Dialogs_Thread_Poster_Content_Text-Input { 112 | Resize: None; 113 | } -------------------------------------------------------------------------------- /Dialog.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("DOMContentLoaded", () => { 2 | let watchers = {}; 3 | 4 | new DOM('@Dialog').forEach((dialog) => { 5 | dialogPolyfill.registerDialog(dialog); 6 | 7 | if (dialog.querySelector('Button[Data-Action="Dialog_Submit"]')) { 8 | dialog.addEventListener("keydown", (event) => { 9 | if (event.ctrlKey && event.keyCode == 13) dialog.querySelector('Button[Data-Action="Dialog_Submit"]').click(); 10 | }); 11 | } 12 | 13 | dialog.querySelectorAll('Dialog *[Required]').forEach((input) => { 14 | input.addEventListener("input", () => { 15 | let result = true; 16 | 17 | dialog.querySelectorAll('Dialog *[Required]').forEach(requiredField => { 18 | if (requiredField.value.replace(/\s/g, "").length == 0) { 19 | result = false; 20 | return; 21 | } 22 | }); 23 | 24 | if (result) { 25 | dialog.querySelector('Button[Data-Action="Dialog_Submit"]').classList.remove("mdl-button--disabled"); 26 | } else { 27 | dialog.querySelector('Button[Data-Action="Dialog_Submit"]').classList.add("mdl-button--disabled"); 28 | } 29 | }); 30 | }); 31 | 32 | dialog.querySelectorAll('Dialog Button[Data-Action="Dialog_Close"]').forEach((btn) => { 33 | btn.addEventListener("click", () => { 34 | btn.offsetParent.close(); 35 | }); 36 | }); 37 | }); 38 | 39 | 40 | 41 | new DOM("#Dialogs_Profile_DeleteConfirmer_Content_Email-Input").addEventListener("input", () => { 42 | if (new DOM("#Dialogs_Profile_DeleteConfirmer_Content_Email-Input").value == base.user.email) { 43 | new DOM("#Dialogs_Profile_DeleteConfirmer_Btns_Yes").classList.remove("mdl-button--disabled"); 44 | } else { 45 | new DOM("#Dialogs_Profile_DeleteConfirmer_Btns_Yes").classList.add("mdl-button--disabled"); 46 | } 47 | }); 48 | 49 | new DOM("#Dialogs_Profile_DeleteConfirmer_Btns_Yes").addEventListener("click", (event) => { 50 | if (new DOM("#Dialogs_Profile_DeleteConfirmer_Content_Email-Input").value == base.user.email) { 51 | base.delete(); 52 | } else { 53 | new DOM("#Dialogs_Profile_DeleteConfirmer_Content_Email").classList.add("is-invalid"); 54 | } 55 | }); 56 | 57 | 58 | 59 | watchers["Dialogs_Profile_InfoViewer_UID"] = { 60 | valueObj: { value: "" }, 61 | watcher: null 62 | }; watchers["Dialogs_Profile_InfoViewer_UID"].watcher = new DOM.Watcher({ 63 | target: watchers["Dialogs_Profile_InfoViewer_UID"].valueObj, 64 | onGet: () => { watchers["Dialogs_Profile_InfoViewer_UID"].valueObj.value = new DOM("#Dialogs_Profile_InfoViewer_UID").value }, 65 | 66 | onChange: (watcher) => { 67 | base.Database.get(base.Database.ONCE, `users/${watcher.newValue}`, (res) => { 68 | new DOM("#Dialogs_Profile_InfoViewer_Content_Photo").dataset.uid = watcher.newValue, 69 | new DOM("#Dialogs_Profile_InfoViewer_Content_Info_Name").textContent = res.userName, 70 | new DOM("#Dialogs_Profile_InfoViewer_Content_Info_Detail").textContent = res.detail; 71 | 72 | while (new DOM("#Dialogs_Profile_InfoViewer_Content_Info_Links").childNodes.length > 0) new DOM("#Dialogs_Profile_InfoViewer_Content_Info_Links").childNodes[0].remove(); 73 | 74 | if (res.links) { 75 | for (let i = 0; i < res.links.length; i++) { 76 | let link = new Component.Dialogs.Profile.InfoViewer.Links.Link(res.links[i].name, res.links[i].url); 77 | 78 | new DOM("#Dialogs_Profile_InfoViewer_Content_Info_Links").appendChild(link); 79 | } 80 | } 81 | }); 82 | } 83 | }); 84 | 85 | 86 | 87 | new DOM("#Dialogs_Thread_DeleteConfirmer_Btns_Yes").addEventListener("click", () => { 88 | base.Database.delete(`threads/${new DOM("#Dialogs_Thread_DeleteConfirmer_TID").value}/`); 89 | parent.document.querySelector("IFrame.mdl-layout__content").contentWindow.postMessage({ code: "Code-Refresh" }, "*"); 90 | 91 | new DOM("#Dialogs_Thread_EditNotify").showModal(); 92 | }); 93 | 94 | 95 | 96 | new DOM("@#Dialogs_Thread_InfoInputter *[Required]").forEach((input) => { 97 | input.addEventListener("input", () => { 98 | let result = true; 99 | 100 | let list = [ 101 | new DOM("#Dialogs_Thread_InfoInputter_Content_Name-Input"), 102 | new DOM("#Dialogs_Thread_InfoInputter_Content_Overview-Input") 103 | ]; 104 | 105 | if (new DOM("#Dialogs_Thread_InfoInputter_Content_Secured-Input").checked) list.push(new DOM("#Dialogs_Thread_InfoInputter_Content_Password-Input")); 106 | 107 | list.forEach(requiredField => { 108 | if (requiredField.value.replace(/\s/g, "").length == 0) { 109 | result = false; 110 | return; 111 | } 112 | }); 113 | 114 | if (result) { 115 | new DOM("#Dialogs_Thread_InfoInputter").querySelectorAll('Button[Data-Action="Dialog_Submit"]').forEach(btn => { 116 | btn.classList.remove("mdl-button--disabled"); 117 | }); 118 | } else { 119 | new DOM("#Dialogs_Thread_InfoInputter").querySelectorAll('Button[Data-Action="Dialog_Submit"]').forEach(btn => { 120 | btn.classList.add("mdl-button--disabled"); 121 | }); 122 | } 123 | }); 124 | }); 125 | 126 | new DOM("#Dialogs_Thread_InfoInputter_Content_Secured-Input").addEventListener("change", (event) => { 127 | let result = true; 128 | 129 | switch (event.target.checked) { 130 | case true: 131 | new DOM("#Dialogs_Thread_InfoInputter_Content_Password").classList.remove("mdl-switch__child-hide"); 132 | 133 | [new DOM("#Dialogs_Thread_InfoInputter_Content_Name-Input"), new DOM("#Dialogs_Thread_InfoInputter_Content_Overview-Input"), new DOM("#Dialogs_Thread_InfoInputter_Content_Password-Input")].forEach(requiredField => { 134 | if (requiredField.value.replace(/\s/g, "").length == 0) { 135 | result = false; 136 | return; 137 | } 138 | }); 139 | 140 | break; 141 | 142 | case false: 143 | new DOM("#Dialogs_Thread_InfoInputter_Content_Password").classList.add("mdl-switch__child-hide"); 144 | 145 | [new DOM("#Dialogs_Thread_InfoInputter_Content_Name-Input"), new DOM("#Dialogs_Thread_InfoInputter_Content_Overview-Input")].forEach(requiredField => { 146 | if (requiredField.value.replace(/\s/g, "").length == 0) { 147 | result = false; 148 | return; 149 | } 150 | }); 151 | 152 | break; 153 | } 154 | 155 | if (result) { 156 | new DOM("#Dialogs_Thread_InfoInputter").querySelectorAll('Button[Data-Action="Dialog_Submit"]').forEach(btn => { 157 | btn.classList.remove("mdl-button--disabled"); 158 | }); 159 | } else { 160 | new DOM("#Dialogs_Thread_InfoInputter").querySelectorAll('Button[Data-Action="Dialog_Submit"]').forEach(btn => { 161 | btn.classList.add("mdl-button--disabled"); 162 | }); 163 | } 164 | }); 165 | 166 | new DOM("#Dialogs_Thread_InfoInputter_Btns_Create").addEventListener("click", (event) => { 167 | base.Database.transaction("threads", (res) => { 168 | let now = new Date().getTime(); 169 | 170 | base.Database.set("threads/" + res.length, { 171 | title: new DOM("#Dialogs_Thread_InfoInputter_Content_Name-Input").value, 172 | overview: new DOM("#Dialogs_Thread_InfoInputter_Content_Overview-Input").value, 173 | detail: new DOM("#Dialogs_Thread_InfoInputter_Content_Detail-Input").value, 174 | 175 | jobs: { 176 | Owner: (() => { 177 | let owner = {}; owner[base.user.uid] = ""; 178 | return owner; 179 | })(), 180 | 181 | Admin: { 182 | 183 | } 184 | }, 185 | 186 | createdAt: now, 187 | 188 | data: [ 189 | { 190 | uid: "!SYSTEM_INFO", 191 | content: new DOM("#Dialogs_Thread_InfoInputter_Content_Name-Input").value, 192 | createdAt: now 193 | } 194 | ], 195 | 196 | password: new DOM("#Dialogs_Thread_InfoInputter_Content_Secured-Input").checked ? Encrypter.encrypt(new DOM("#Dialogs_Thread_InfoInputter_Content_Password-Input").value) : "" 197 | }); 198 | 199 | new DOM("#Dialogs_Thread_InfoInputter").close(); 200 | parent.document.querySelector("IFrame.mdl-layout__content").src = "Thread/Viewer/?tid=" + res.length; 201 | }); 202 | }); 203 | 204 | new DOM("#Dialogs_Thread_InfoInputter_Btns_Edit").addEventListener("click", (event) => { 205 | base.Database.update(`threads/${new DOM("#Dialogs_Thread_InfoInputter_TID").value}/`, { 206 | title: new DOM("#Dialogs_Thread_InfoInputter_Content_Name-Input").value, 207 | overview: new DOM("#Dialogs_Thread_InfoInputter_Content_Overview-Input").value, 208 | detail: new DOM("#Dialogs_Thread_InfoInputter_Content_Detail-Input").value, 209 | password: new DOM("#Dialogs_Thread_InfoInputter_Content_Secured-Input").checked ? Encrypter.encrypt(new DOM("#Dialogs_Thread_InfoInputter_Content_Password-Input").value) : "" 210 | }); 211 | 212 | new DOM("#Dialogs_Thread_InfoInputter").close(); 213 | new DOM("#Dialogs_Thread_EditNotify").showModal(); 214 | }); 215 | 216 | 217 | 218 | new DOM("#Dialogs_Thread_PasswordConfirmer_Btns_OK").addEventListener("click", (event) => { 219 | if (Encrypter.encrypt(new DOM("#Dialogs_Thread_PasswordConfirmer_Content_Password-Input").value) == new DOM("#Dialogs_Thread_PasswordConfirmer_Password").value) { 220 | sessionStorage.setItem("com.GenbuProject.SimpleThread.currentPassword", new DOM("#Dialogs_Thread_PasswordConfirmer_Content_Password-Input").value); 221 | new DOM("$IFrame.mdl-layout__content").src = new DOM("#Dialogs_Thread_PasswordConfirmer_Link").value; 222 | 223 | new DOM("#Dialogs_Thread_PasswordConfirmer_Link").value = "", 224 | new DOM("#Dialogs_Thread_PasswordConfirmer_Password").value = ""; 225 | } else { 226 | new DOM("#Dialogs_Thread_PasswordConfirmer_Content_Password").classList.add("is-invalid"); 227 | } 228 | }); 229 | 230 | new DOM("#Dialogs_Thread_PasswordConfirmer_Btns_Cancel").addEventListener("click", (event) => { 231 | new DOM("$IFrame.mdl-layout__content").src = "/SimpleThread/Thread/"; 232 | }); 233 | 234 | 235 | 236 | watchers["Dialogs_Thread_InfoViewer_TID"] = { 237 | valueObj: { value: "0" }, 238 | watcher: null 239 | }; watchers["Dialogs_Thread_InfoViewer_TID"].watcher = new DOM.Watcher({ 240 | target: watchers["Dialogs_Thread_InfoViewer_TID"].valueObj, 241 | onGet: () => { watchers["Dialogs_Thread_InfoViewer_TID"].valueObj.value = new DOM("#Dialogs_Thread_InfoViewer_TID").value }, 242 | 243 | onChange: (watcher) => { 244 | base.Database.get(base.Database.ONCE, `threads/${watcher.newValue}`, (res) => { 245 | new DOM("#Dialogs_Thread_InfoViewer_Content_Name").textContent = res.title, 246 | new DOM("#Dialogs_Thread_InfoViewer_Content_Overview").textContent = res.overview, 247 | new DOM("#Dialogs_Thread_InfoViewer_Content_Detail").textContent = res.detail; 248 | 249 | URL.filter(new DOM("#Dialogs_Thread_InfoViewer_Content_Overview").textContent).forEach((urlString) => { 250 | new DOM("#Dialogs_Thread_InfoViewer_Content_Overview").innerHTML = new DOM("#Dialogs_Thread_InfoViewer_Content_Overview").innerHTML.replace(urlString, `${urlString}`); 251 | }); 252 | 253 | URL.filter(new DOM("#Dialogs_Thread_InfoViewer_Content_Detail").textContent).forEach((urlString) => { 254 | new DOM("#Dialogs_Thread_InfoViewer_Content_Detail").innerHTML = new DOM("#Dialogs_Thread_InfoViewer_Content_Detail").innerHTML.replace(urlString, `${urlString}`); 255 | }); 256 | }); 257 | } 258 | }); 259 | 260 | 261 | 262 | new DOM("#Dialogs_Thread_Poster_Menu_MenuItem-EmbedLink").addEventListener("click", () => { 263 | new DOM("#Dialogs_Thread_Poster_LinkEmbedder").showModal(); 264 | }); 265 | 266 | new DOM("#Dialogs_Thread_Poster_Menu_MenuItem-EmbedImage").addEventListener("click", () => { 267 | new DOM("#Dialogs_Thread_Poster").close(); 268 | 269 | let picker = new Picker.PhotoPicker(data => { 270 | console.log(data); 271 | 272 | switch (data[google.picker.Response.ACTION]) { 273 | case google.picker.Action.CANCEL: 274 | case google.picker.Action.PICKED: 275 | new DOM("#Dialogs_Thread_Poster").showModal(); 276 | break; 277 | } 278 | }); 279 | 280 | picker.show(); 281 | }); 282 | 283 | new DOM("#Dialogs_Thread_Poster_Menu_MenuItem-EmbedFile").addEventListener("click", () => { 284 | new DOM("#Dialogs_Thread_Poster").close(); 285 | 286 | let picker = new Picker.FilePicker(data => { 287 | console.log(data); 288 | 289 | switch (data[google.picker.Response.ACTION]) { 290 | case google.picker.Action.CANCEL: 291 | case google.picker.Action.PICKED: 292 | new DOM("#Dialogs_Thread_Poster").showModal(); 293 | break; 294 | } 295 | }); 296 | 297 | picker.show(); 298 | }); 299 | 300 | new DOM("#Dialogs_Thread_Poster_Content_Text-Input").addEventListener("keydown", (event) => { 301 | let inputter = event.target; 302 | 303 | let selectionStart = inputter.selectionStart, 304 | selectionEnd = inputter.selectionEnd; 305 | 306 | switch (event.keyCode) { 307 | case 9: 308 | event.preventDefault(); 309 | 310 | inputter.value = `${inputter.value.slice(0, selectionStart)}\t${inputter.value.slice(selectionEnd)}`; 311 | inputter.setSelectionRange(selectionStart + 1, selectionStart + 1); 312 | 313 | new DOM("#Dialogs_Thread_Poster_Content_Text").classList.add("is-dirty"); 314 | 315 | break; 316 | } 317 | }); 318 | 319 | new DOM("#Dialogs_Thread_Poster_Btns_OK").addEventListener("click", (event) => { 320 | base.Database.transaction("threads/" + new DOM("#Dialogs_Thread_Poster_TID").value + "/data", (res) => { 321 | base.Database.set("threads/" + new DOM("#Dialogs_Thread_Poster_TID").value + "/data/" + res.length, { 322 | uid: base.user.uid, 323 | content: new DOM("#Dialogs_Thread_Poster_Content_Text-Input").value, 324 | createdAt: new Date().getTime() 325 | }); 326 | 327 | new DOM("#Dialogs_Thread_Poster_Btns_OK").classList.add("mdl-button--disabled"), 328 | new DOM("#Dialogs_Thread_Poster_Content_Text").classList.remove("is-dirty"), 329 | new DOM("#Dialogs_Thread_Poster_Content_Text-Input").value = ""; 330 | 331 | new DOM("#Page").contentDocument.querySelector("#FlowPanel_Btns_CreatePost").removeAttribute("Disabled"); 332 | 333 | new DOM("#Dialogs_Thread_Poster").close(); 334 | }); 335 | }); 336 | 337 | new DOM("#Dialogs_Thread_Poster_Btns_Cancel").addEventListener("click", () => { 338 | new DOM("#Dialogs_Thread_Poster_Btns_OK").classList.add("mdl-button--disabled"), 339 | new DOM("#Dialogs_Thread_Poster_Content_Text").classList.remove("is-dirty"), 340 | new DOM("#Dialogs_Thread_Poster_Content_Text-Input").value = ""; 341 | 342 | new DOM("#Page").contentDocument.querySelector("#FlowPanel_Btns_CreatePost").removeAttribute("Disabled"); 343 | }); 344 | 345 | 346 | 347 | for (let watcherName in watchers) DOM.Watcher.addWatcher(watchers[watcherName].watcher); 348 | }); -------------------------------------------------------------------------------- /Error/401/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 401 Required Authorization 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 401 Required Authorization 20 |
21 | 22 |
23 |

24 | お探しのページの閲覧にはログインが必要です。
25 | ログインしてからご利用ください。 26 |

27 | 28 | 5秒後にメインページへ移動します… 29 |
30 | 31 | -------------------------------------------------------------------------------- /Error/403.10/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 403 Not Allowed 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 403 Not Allowed 20 |
21 | 22 |
23 |

24 | 正規ルートでのアクセスが確認できませんでした。 25 |

26 | 27 | 5秒後にメインページへ移動します… 28 |
29 | 30 | -------------------------------------------------------------------------------- /Error/406/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 406 Not Acceptable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 406 Not Acceptable 20 |
21 | 22 |
23 |

24 | リクエストを正常に認識できませんでした。
25 | 不正なリクエストが送信された可能性があります。 26 |

27 | 28 | 5秒後にメインページへ移動します… 29 |
30 | 31 | -------------------------------------------------------------------------------- /Error/Back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GenbuProject/SimpleThread/60bfc1ba5756be70ca03dad1a7416b56442496ae/Error/Back.jpg -------------------------------------------------------------------------------- /Error/Main.css: -------------------------------------------------------------------------------- 1 | Body { 2 | Margin: 5% 10% 0 10%; 3 | 4 | Border: Thin Solid LightGray; 5 | Border-Radius: 10px; 6 | } 7 | 8 | Body > Div { 9 | Padding: 10px; 10 | } 11 | 12 | .Title { 13 | Text-Align: Center; 14 | Font-Size: XX-Large; 15 | 16 | BackGround: RGBA(173, 216, 230, 0.25); 17 | Border-Bottom: Thin Solid LightBlue; 18 | } 19 | 20 | .Title > Img { 21 | Vertical-Align: Middle; 22 | } 23 | 24 | .Message > P { 25 | Margin-Top: 0; 26 | } -------------------------------------------------------------------------------- /Error/Main.js: -------------------------------------------------------------------------------- 1 | setTimeout(() => { 2 | window.open("/SimpleThread/", "_parent"); 3 | }, 5000); -------------------------------------------------------------------------------- /Error/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GenbuProject/SimpleThread/60bfc1ba5756be70ca03dad1a7416b56442496ae/Error/favicon.ico -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /Main.css: -------------------------------------------------------------------------------- 1 | #Header_Title { 2 | Max-Width: 45%; 3 | 4 | Overflow: Hidden; 5 | Text-Overflow: Ellipsis; 6 | White-Space: NoWrap; 7 | } -------------------------------------------------------------------------------- /Main.js: -------------------------------------------------------------------------------- 1 | window.base = new DBLoader("assets/firebase.json", (user) => { 2 | window.gapi.load("picker", () => { 3 | window.Picker = class Picker extends google.picker.PickerBuilder { 4 | static get PhotoPicker () { 5 | return class PhotoPicker extends window.Picker { 6 | constructor (onSelect = (data) => {}) { 7 | super(onSelect); 8 | super.addView(google.picker.ViewId.PHOTOS); 9 | } 10 | } 11 | } 12 | 13 | static get FilePicker () { 14 | return class FilePicker extends window.Picker { 15 | constructor (onSelect = (data) => {}) { 16 | super(onSelect); 17 | super.addView(google.picker.ViewId.DOCS); 18 | } 19 | } 20 | } 21 | 22 | 23 | 24 | constructor (onSelect = (data) => {}) { 25 | super(); 26 | 27 | super.setOAuthToken(base.accessToken), 28 | super.setDeveloperKey(base.option.apiKey), 29 | super.setCallback(onSelect); 30 | } 31 | 32 | show () { 33 | let picker = this.picker = this.build(); 34 | picker.setVisible(true); 35 | } 36 | 37 | dismiss () { 38 | this.picker.setVisible(false); 39 | } 40 | } 41 | }); 42 | 43 | 44 | 45 | if (user) { 46 | new DOM("#Header_SignInOut").dataset.locales = "main.signOut"; 47 | 48 | base.Database.getInfo(base.Database.ONCE, `users/${user.uid}`, (res) => { 49 | new DOM('@A[UUID="ProfilePhoto-Btn"]').forEach((btn) => { 50 | btn.dataset.uid = user.uid; 51 | }); 52 | 53 | if (!res.exists()) { 54 | base.Database.set(`users/${user.uid}`, { 55 | gplusName: user.providerData[0].displayName, 56 | gplusPhoto: user.photoURL, 57 | userName: user.providerData[0].displayName, 58 | detail: "", 59 | links: [] 60 | }); 61 | 62 | new DOM("#Dialogs_Account_CreateNotify").showModal(); 63 | } else { 64 | base.Database.update(`users/${user.uid}`, { 65 | gplusName: user.providerData[0].displayName, 66 | gplusPhoto: user.photoURL 67 | }); 68 | } 69 | }); 70 | 71 | base.Database.get(base.Database.ONCE, `users/${base.user.uid}`, (res) => { 72 | new DOM("#Dialogs_Thread_Poster_Header_ActorPhoto").dataset.uid = base.user.uid; 73 | new DOM("#Dialogs_Thread_Poster_Header_Actor").textContent = res.userName; 74 | }); 75 | } else { 76 | window.addEventListener("DOMContentLoaded", () => { 77 | new DOM('@*[UUID="ProfilePhoto-Btn"]').forEach((btn) => { 78 | btn.setAttribute("Disabled", ""); 79 | }); 80 | }); 81 | } 82 | 83 | locales.applyToElement(new DOM("#Header_SignInOut")); 84 | 85 | base.Database.get(base.Database.ONCE, "users", (res) => { 86 | for (let uid in res) { 87 | let photoStyle = new Component.Styles.ProfilePhotoManager(uid, res[uid].gplusPhoto); 88 | 89 | document.head.appendChild(photoStyle); 90 | } 91 | }); 92 | 93 | 94 | 95 | let querys = location.querySort(); 96 | 97 | if (querys.TID) { 98 | new DOM("$IFrame.mdl-layout__content").src = `Thread/Viewer/?tid=${querys.TID}`; 99 | } 100 | }); 101 | 102 | window.terminal = (() => { 103 | let terminal = new Worker("Terminal.js"); 104 | terminal.addEventListener("message", (event) => { 105 | let message = event.data || {}; 106 | message.code = message.code || "", 107 | message.data = !(message.data != false && !message.data) ? message.data : ""; 108 | 109 | switch (message.code) { 110 | case "Code-SendHasLogined_1": 111 | terminal.postMessage({ 112 | code: "Code-SendHasLogined_2", 113 | data: base.user ? true : false 114 | }); 115 | 116 | break; 117 | } 118 | }); 119 | 120 | return terminal; 121 | })(); 122 | 123 | window.locales = (() => { 124 | let locales = new LangLoader(); 125 | locales.load(localStorage.getItem("com.GenbuProject.SimpleThread.currentLang")); 126 | 127 | return locales; 128 | })(); 129 | 130 | 131 | 132 | window.addEventListener("DOMContentLoaded", () => { 133 | new DOM("$IFrame#Page").addEventListener("load", () => { 134 | try { 135 | !new DOM("#Drawer") || new DOM("#Drawer").classList.remove("is-visible"), 136 | !new DOM("$Div.mdl-layout__obfuscator") || new DOM("$Div.mdl-layout__obfuscator").classList.remove("is-visible"); 137 | } catch (error) {} 138 | 139 | if (new DOM("$IFrame#Page").contentWindow.location.pathname != "/SimpleThread/Thread/Viewer/") locales.applyToElement(new DOM("#Header_Title")); 140 | }); 141 | 142 | new DOM("#Header_SignInOut").addEventListener("click", () => { 143 | switch (new DOM("#Header_SignInOut").dataset.locales) { 144 | case "main.signIn": 145 | base.signInWithRedirect(base.SIGNINTYPE.GOOGLE, base.option.scope); 146 | break; 147 | 148 | case "main.signOut": 149 | base.signOut(); 150 | break; 151 | 152 | default: 153 | alert("Got to Default."); 154 | break; 155 | } 156 | }); 157 | }); -------------------------------------------------------------------------------- /Multi Replacer.cfg: -------------------------------------------------------------------------------- 1 | [Config] 2 | Value1=SimpleThread-Debug 3 | Value2=SimpleThread 4 | Extensions=*.html;*.js;*.css;*.md 5 | Recent=2 6 | -------------------------------------------------------------------------------- /Profile/Profile.css: -------------------------------------------------------------------------------- 1 | * { 2 | Font-Family: "Meiryo UI"; 3 | } 4 | 5 | 6 | 7 | #Profile { 8 | Display: Flex; 9 | Flex-Direction: Row; 10 | Align-Items: Flex-Start; 11 | } 12 | 13 | #Profile_Photo { 14 | Width: 20%; 15 | 16 | Margin-Right: 5%; 17 | 18 | Border-Radius: 100%; 19 | } 20 | 21 | #Profile_Photo::Before { 22 | Content: ""; 23 | 24 | Display: Block; 25 | Padding-Top: 100%; 26 | } 27 | 28 | #Profile_Info { 29 | Display: Flex; 30 | Flex-Direction: Column; 31 | 32 | Flex: 1; 33 | 34 | Padding: 2vmin 0; 35 | } 36 | 37 | #Profile_Info > * { 38 | Width: 100%; 39 | } -------------------------------------------------------------------------------- /Profile/Profile.js: -------------------------------------------------------------------------------- 1 | terminal.addEventListener("message", (event) => { 2 | let message = event.data || {}; 3 | message.code = message.code || "", 4 | message.data = !(message.data != false && !message.data) ? message.data : ""; 5 | 6 | switch (message.code) { 7 | case "Code-SendHasLogined": 8 | if (!message.data) location.href = "/SimpleThread/Error/401/"; 9 | break; 10 | } 11 | }); 12 | 13 | window.addEventListener("DOMContentLoaded", () => { 14 | new DOM("#Profile_Photo").dataset.uid = base.user.uid; 15 | 16 | new DOM("#Profile_Info_URL").childNodes[0].querySelector('Span.mdl-list__item-primary-content').dataset.locales = "profile.url"; 17 | locales.applyToElement(new DOM("#Profile_Info_URL").childNodes[0].querySelector('Span.mdl-list__item-primary-content')); 18 | 19 | new DOM("#Profile_Info_URL_Add").addEventListener("click", () => { 20 | new DOM("#Profile_Info_URL").childNodes[new DOM("#Profile_Info_URL").childNodes.length - 1].querySelector('Span.mdl-list__item-primary-content > *:nth-Child(1) > Label').dataset.locales = "profile.url.title", 21 | new DOM("#Profile_Info_URL").childNodes[new DOM("#Profile_Info_URL").childNodes.length - 1].querySelector('Span.mdl-list__item-primary-content > *:nth-Child(2) > Label').dataset.locales = "profile.url.value"; 22 | locales.applyToElement(new DOM("#Profile_Info_URL").childNodes[new DOM("#Profile_Info_URL").childNodes.length - 1].querySelector('Span.mdl-list__item-primary-content > *:nth-Child(1) > Label')), 23 | locales.applyToElement(new DOM("#Profile_Info_URL").childNodes[new DOM("#Profile_Info_URL").childNodes.length - 1].querySelector('Span.mdl-list__item-primary-content > *:nth-Child(2) > Label')); 24 | }); 25 | 26 | 27 | 28 | base.Database.get(base.Database.ONCE, "users", (res) => { 29 | for (let uid in res) { 30 | let photoStyle = new Component.Styles.ProfilePhotoManager(uid, res[uid].gplusPhoto); 31 | 32 | document.head.appendChild(photoStyle); 33 | } 34 | }); 35 | 36 | base.Database.get(base.Database.INTERVAL, "users/" + base.user.uid, (res) => { 37 | res.links = res.links || []; 38 | 39 | new DOM("#Profile_Info_Name").classList.add("is-dirty"), 40 | new DOM("#Profile_Info_Name-Input").value = res.userName; 41 | new DOM("#Profile_Info_Detail").classList.add("is-dirty"), 42 | new DOM("#Profile_Info_Detail-Input").value = res.detail; 43 | 44 | (() => { 45 | let clientListLength = new DOM("#Profile_Info_URL").dataset.listlength; 46 | 47 | if (res.links.length - clientListLength > 0) { 48 | for (let i = 0; i < res.links.length - clientListLength; i++) { 49 | new DOM("#Profile_Info_URL_Add").click(); 50 | } 51 | } else { 52 | for (let i = 0; i < clientListLength - res.links.length; i++) { 53 | new DOM("#Profile_Info_URL").children[0].querySelector('Button[ID*="Remove"]').click(); 54 | } 55 | } 56 | 57 | for (let i = 0; i < res.links.length; i++) { 58 | let currentList = new DOM("#Profile_Info_URL").querySelector('Li[Data-ItemID="' + i + '"]'); 59 | currentList.querySelectorAll("Div").forEach((container) => { 60 | container.classList.add("is-dirty"); 61 | }); 62 | 63 | currentList.querySelector('Input[Data-FieldID="0"]').value = res.links[i].name, 64 | currentList.querySelector('Input[Data-FieldID="1"]').value = res.links[i].url; 65 | } 66 | })(); 67 | }); 68 | 69 | 70 | 71 | new DOM("#Profile_Info_Btns_Save").addEventListener("click", () => { 72 | base.Database.update("users/" + base.user.uid, { 73 | userName: new DOM("#Profile_Info_Name-Input").value, 74 | detail: new DOM("#Profile_Info_Detail-Input").value, 75 | 76 | links: (() => { 77 | let links = []; 78 | 79 | for (let i = 0; i < new DOM("#Profile_Info_URL").dataset.listlength; i++) { 80 | let currentList = new DOM("#Profile_Info_URL").querySelector('Li[Data-ItemID="' + i + '"]'); 81 | 82 | links.push({ 83 | name: currentList.querySelector('Input[Data-FieldID="0"]').value, 84 | url: currentList.querySelector('Input[Data-FieldID="1"]').value 85 | }); 86 | } 87 | 88 | return links; 89 | })() 90 | }); 91 | }); 92 | 93 | 94 | 95 | new DOM("#Profile_Info_Btns_Save").addEventListener("click", () => { 96 | doc.querySelector("#Dialogs_Profile_ChangeNotify").showModal(); 97 | }); 98 | 99 | new DOM("#Profile_Info_Btns_Delete").addEventListener("click", () => { 100 | doc.querySelector("#Dialogs_Profile_DeleteConfirmer").showModal(); 101 | }); 102 | }); -------------------------------------------------------------------------------- /Profile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple Thread == プロフィール 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 | 46 |
47 |
48 | 49 | 50 |
51 | 52 |
53 | 54 | 55 |
56 | 57 |
    58 | 59 |
    60 | 61 | 62 |
    63 |
    64 |
    65 |
    66 | 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Thread 2 | 3 | ## 1. Simple Thread とは? 4 | > "Be Simple, Be Free" をモットーに掲げる、SNSサービス及びフレームワークの総称です。 5 | > 6 | > 詳しい動作につきましては[こちら](https://genbuproject.github.io/SimpleThread/)をご覧下さい。 7 | 8 | ## 2. Simple Thread の歴史 9 | > | Date | Detail | 10 | > | ---------- | ---------- | 11 | > | 2017/01/01 | `v0.1`(旧`v1`)の構想を開始 | 12 | > | 2017/08/24 | `v1.0`(旧`v2`)の構想を開始 | 13 | > | 2017/08/30 | `v1.0`(旧`v2`)リリース | 14 | > | 2017/09/05 | `v1.1`(旧`v3`)の構想を開始 | 15 | > | 2017/12/01 | `v1.1`(旧`v3_RC1`)リリース | 16 | 17 | ## 3. 詳細情報 18 | > * 最新バージョン 19 | > * Stable ... `v1.0` 20 | > * Latest ... `v1.1` -------------------------------------------------------------------------------- /ServerInfo/ServerInfo.css: -------------------------------------------------------------------------------- 1 | #Top_Logo { 2 | Width: 100%; 3 | 4 | Margin-Bottom: 5vmax; 5 | } 6 | 7 | #ServerInfo { 8 | Width: 100%; 9 | } -------------------------------------------------------------------------------- /ServerInfo/ServerInfo.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("DOMContentLoaded", () => { 2 | base.Database.get(base.Database.INTERVAL, "users", (res) => { 3 | let length = 0; 4 | 5 | for (let key in res) length++; 6 | 7 | new DOM("#ServerInfo_Users").textContent = length - 1; 8 | }); 9 | 10 | base.Database.get(base.Database.INTERVAL, "threads", (res) => { 11 | res = res.filter((thread) => { 12 | if (thread !== "!SYSTEM") return true; 13 | }); 14 | 15 | new DOM("#ServerInfo_Threads").textContent = res.length; 16 | }); 17 | }); -------------------------------------------------------------------------------- /ServerInfo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Simple Thread == サーバー情報 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 38 | 39 | 40 | 41 |
    42 |
    43 | 44 | 45 |

    Server Info

    46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
    項目
    サービス名称Simple Thread
    ユーザー数0
    スレッド数0
    最終更新日2017/11/29
    77 |
    78 |
    79 | 80 | -------------------------------------------------------------------------------- /Terminal.js: -------------------------------------------------------------------------------- 1 | self.addEventListener("message", (event) => { 2 | let message = event.data || {}; 3 | message.code = message.code || "", 4 | message.data = !(message.data != false && !message.data) ? message.data : ""; 5 | 6 | switch (message.code) { 7 | case "Code-Connected": 8 | break; 9 | 10 | case "Code-RequestHasLogined": 11 | self.postMessage({ code: "Code-SendHasLogined_1" }); 12 | break; 13 | 14 | case "Code-SendHasLogined_1": 15 | break; 16 | 17 | case "Code-SendHasLogined_2": 18 | self.postMessage({ 19 | code: "Code-SendHasLogined", 20 | data: message.data 21 | }); 22 | 23 | break; 24 | 25 | case "Code-SendHasLogined": 26 | break; 27 | } 28 | }); -------------------------------------------------------------------------------- /Thread/Thread.css: -------------------------------------------------------------------------------- 1 | #Threadlist_Tab_Reload { 2 | Display: Inline-Flex; 3 | Align-Items: Center; 4 | } 5 | 6 | #Threadlist_Search_Searcher_Container { 7 | Width: 100%; 8 | } 9 | 10 | #Threadlist > Div[Disabled] { 11 | Display: None; 12 | } 13 | 14 | #Threadlist *[Data-Component^="Threadlist_Thread"] { 15 | Text-Decoration: None; 16 | Cursor: Pointer; 17 | } 18 | 19 | #Threadlist *[Data-Component^="Threadlist_Thread"][Disabled] { 20 | Display: None; 21 | } 22 | 23 | #Threadlist *[Data-Component^="Threadlist_Thread"] A { 24 | Color: Inherit; 25 | Font-Weight: Inherit; 26 | } 27 | 28 | #Threadlist *[Data-Component="Threadlist_Thread-Admin"] { 29 | Position: Relative; 30 | } -------------------------------------------------------------------------------- /Thread/Thread.js: -------------------------------------------------------------------------------- 1 | class Util { 2 | static refreshThreadList () { 3 | base.Database.get(base.Database.ONCE, "threads", (res) => { 4 | res = res.filter((thread, index, parent) => { 5 | if (thread !== "!SYSTEM") { 6 | thread.tid = index; 7 | return true; 8 | } 9 | }); 10 | 11 | for (let i = 0; i < res.length; i++) { 12 | let thread; 13 | 14 | if (!base.user) { 15 | thread = new Component.Threadlist.Thread(res[i].tid, res[i].title, res[i].overview, false, res[i].password); 16 | } else { 17 | thread = new Component.Threadlist.Thread(res[i].tid, res[i].title, res[i].overview, res[i].jobs.Owner.hasOwnProperty(base.user.uid), res[i].password); 18 | } 19 | 20 | new DOM("#Threadlist_Search").appendChild(thread); 21 | 22 | if (base.user && res[i].jobs.Owner.hasOwnProperty(base.user.uid)) { 23 | new DOM("#Threadlist_Admin").appendChild(thread); 24 | 25 | thread.querySelector(`*[Data-Component="${Component.Threadlist.Thread.UUIDS.ADMIN.MENU.EDIT}"]`).addEventListener("click", () => { 26 | doc.querySelector("#Dialogs_Thread_InfoInputter_TID").value = res[i].tid; 27 | 28 | base.Database.get(base.Database.ONCE, `threads/${res[i].tid}/`, res => { 29 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Name-Input").value = res.title, 30 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Overview-Input").value = res.overview, 31 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Detail-Input").value = res.detail, 32 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Password-Input").value = res.password ? " " : ""; 33 | 34 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Name").classList.remove("is-invalid"), 35 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Name").classList.add("is-dirty"), 36 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Overview").classList.remove("is-invalid"), 37 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Overview").classList.add("is-dirty"), 38 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Detail").classList.remove("is-invalid"), 39 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Detail").classList.add("is-dirty"), 40 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Password").classList.remove("is-invalid"), 41 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Password").classList.add("is-dirty"); 42 | 43 | if (res.password) { 44 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Secured").classList.add("is-checked"), 45 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Secured-Input").checked = true, 46 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Password").classList.remove("mdl-switch__child-hide"); 47 | } else { 48 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Secured").classList.remove("is-checked"), 49 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Secured-Input").checked = false, 50 | doc.querySelector("#Dialogs_Thread_InfoInputter_Content_Password").classList.add("mdl-switch__child-hide"); 51 | } 52 | }); 53 | }); 54 | 55 | thread.querySelector(`*[Data-Component="${Component.Threadlist.Thread.UUIDS.ADMIN.MENU.DELETE}"]`).addEventListener("click", () => { 56 | doc.querySelector("#Dialogs_Thread_DeleteConfirmer_TID").value = res[i].tid; 57 | }); 58 | } 59 | } 60 | 61 | componentHandler.upgradeDom(); 62 | }); 63 | } 64 | } 65 | 66 | window.addEventListener("message", event => { 67 | switch (event.data.code) { 68 | case "Code-Refresh": 69 | while (new DOM("#Threadlist_Search").children.length > 1) new DOM("#Threadlist_Search").children[1].remove(); 70 | while (new DOM("#Threadlist_Admin").children.length > 1) new DOM("#Threadlist_Admin").children[1].remove(); 71 | 72 | Util.refreshThreadList(); 73 | 74 | break; 75 | } 76 | }); 77 | 78 | window.addEventListener("DOMContentLoaded", () => { 79 | if (!base.user) { 80 | new DOM("$#Threadlist_Tab_Admin").setAttribute("Disabled", ""), 81 | new DOM("$#Threadlist_Admin").setAttribute("Disabled", ""); 82 | } 83 | 84 | Util.refreshThreadList(); 85 | 86 | 87 | 88 | new DOM("#Threadlist_Search_Searcher_Container-Input").addEventListener("input", (event) => { 89 | let list = Array.from(new DOM("#Threadlist_Search").children).splice(1); 90 | list.forEach((thread) => { 91 | if (thread.querySelector("Span:Not(.mdl-list__item-primary-content)").textContent.toLowerCase().indexOf(event.target.value.toLowerCase()) == -1) { 92 | thread.setAttribute("Disabled", ""); 93 | } else { 94 | thread.removeAttribute("Disabled"); 95 | } 96 | }); 97 | }); 98 | 99 | new DOM("#Threadlist_Tab_Reload").addEventListener("click", (event) => { 100 | while (new DOM("#Threadlist_Search").children.length > 1) new DOM("#Threadlist_Search").children[1].remove(); 101 | while (new DOM("#Threadlist_Admin").children.length > 1) new DOM("#Threadlist_Admin").children[1].remove(); 102 | 103 | Util.refreshThreadList(); 104 | }); 105 | 106 | 107 | 108 | new DOM("#Threadlist_Admin_Create").addEventListener("click", () => { 109 | doc.querySelector("#Dialogs_Thread_InfoInputter_Btns_Create").removeAttribute("Disabled"), 110 | doc.querySelector("#Dialogs_Thread_InfoInputter_Btns_Edit").setAttribute("Disabled", ""); 111 | 112 | doc.querySelector("#Dialogs_Thread_InfoInputter").showModal(); 113 | }); 114 | }); -------------------------------------------------------------------------------- /Thread/Viewer/Auth/Auth.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GenbuProject/SimpleThread/60bfc1ba5756be70ca03dad1a7416b56442496ae/Thread/Viewer/Auth/Auth.css -------------------------------------------------------------------------------- /Thread/Viewer/Auth/Auth.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("DOMContentLoaded", () => { 2 | if (doc.querySelector("#Dialogs_Thread_PasswordConfirmer_Link").value) { 3 | doc.querySelector("#Dialogs_Thread_PasswordConfirmer").showModal(); 4 | } else { 5 | doc.querySelector("IFrame.mdl-layout__content").src = "Thread/"; 6 | } 7 | 8 | let querys = location.querySort(); 9 | 10 | base.Database.get(base.Database.ONCE, "threads/" + querys.TID, (res) => { 11 | doc.querySelector("#Dialogs_Thread_PasswordConfirmer_Password").value = res.password; 12 | }); 13 | }); -------------------------------------------------------------------------------- /Thread/Viewer/Auth/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple Thread == Authorization 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Thread/Viewer/Viewer.css: -------------------------------------------------------------------------------- 1 | Div[Data-Component="Thread_Post"], Div[Data-Component="Thread_Post-Mine"] { 2 | Width: 100%; 3 | Min-Height: Auto; 4 | 5 | Margin: 5vh 0; 6 | } 7 | 8 | Div[Data-Component="Thread_Post_Header"] > * { 9 | Display: Flex; 10 | Flex-Direction: Row; 11 | Align-Items: Center; 12 | 13 | Font-Size: Medium; 14 | } 15 | 16 | A[Data-Component="Thread_Post_Header_ActorPhoto"] { 17 | Background: Transparent Center / Cover; 18 | } 19 | 20 | A[Data-Component="Thread_Post_Header_ActorPhoto"][Data-UID^="!SYSTEM"] + Span[Data-Component="Thread_Post_Header_Actor"] { 21 | Color: OrangeRed; 22 | Text-Decoration: Underline; 23 | } 24 | 25 | A[Data-Component="Thread_Post_Header_ActorPhoto"][Disabled] { 26 | Display: None; 27 | } 28 | 29 | Span[Data-Component="Thread_Post_Header_Actor"] { 30 | Align-Self: Auto; 31 | 32 | Margin-Left: 0.5em; 33 | } 34 | 35 | Span[Data-Component="Thread_Post_Header_CreatedAt"] { 36 | Color: #BDBDBD; 37 | Font-Size: 80%; 38 | } 39 | 40 | Div[Data-Component="Thread_Post_Content"] { 41 | White-Space: Pre-Wrap; 42 | } 43 | 44 | Div[Data-Component="Thread_Post_Content"] * { 45 | Max-Width: 100%; 46 | } 47 | 48 | 49 | 50 | #FlowPanel_Btns { 51 | Position: Fixed; 52 | Right: 0; 53 | Bottom: 0; 54 | 55 | Display: Flex; 56 | Flex-Direction: Row-Reverse; 57 | 58 | Padding: 5vh 4vw; 59 | Z-Index: 100; 60 | } 61 | 62 | #FlowPanel_Btns *[Disabled] { 63 | Display: None; 64 | } 65 | 66 | #FlowPanel_Btns > Div { 67 | Margin-Left: 2.5vw; 68 | } 69 | 70 | #FlowPanel_Btns > Div:Last-Child { 71 | Margin-Left: 0; 72 | } 73 | 74 | .FlowPanel_Btns_Row { 75 | Display: Flex; 76 | Flex-Direction: Column; 77 | } 78 | 79 | .FlowPanel_Btns_Row > * { 80 | Margin-Bottom: 2.5vw; 81 | } 82 | 83 | .FlowPanel_Btns_Row > *:Last-Child { 84 | Margin-Bottom: 0; 85 | } -------------------------------------------------------------------------------- /Thread/Viewer/Viewer.js: -------------------------------------------------------------------------------- 1 | const TID = location.querySort().TID; 2 | 3 | window.addEventListener("DOMContentLoaded", () => { 4 | if (!TID) { 5 | location.href = "/SimpleThread/Error/406/"; 6 | } 7 | 8 | if (!base.user) { 9 | new DOM("#FlowPanel_Btns_CreatePost").setAttribute("Disabled", ""); 10 | } 11 | 12 | 13 | 14 | doc.querySelector("#Dialogs_Thread_InfoViewer_TID").value = TID; 15 | doc.querySelector("#Dialogs_Thread_Poster_TID").value = TID; 16 | 17 | base.Database.get(base.Database.ONCE, "threads/" + TID, (res) => { 18 | doc.querySelector("#Header_Title").textContent = `${res.title}`; 19 | 20 | if (res.password && res.password != Encrypter.encrypt(sessionStorage.getItem("com.GenbuProject.SimpleThread.currentPassword") || "")) { 21 | doc.querySelector("#Dialogs_Thread_PasswordConfirmer_Link").value = location.href; 22 | 23 | doc.querySelector("IFrame.mdl-layout__content").src = `Thread/Viewer/Auth/?tid=${TID}`; 24 | } 25 | }); 26 | 27 | base.Database.get(base.Database.ONCE, "users", (res) => { 28 | for (let uid in res) document.head.appendChild(new Component.Styles.ProfilePhotoManager(uid, res[uid].gplusPhoto)); 29 | }); 30 | 31 | base.Database.get(base.Database.INTERVAL, "threads/" + TID + "/data", (res) => { 32 | resForIncrease = res, resForDecrease = res; 33 | 34 | resForIncrease = resForIncrease.filter((post, index, parent) => { 35 | if (post) { 36 | post.pid = index; 37 | return true; 38 | } 39 | }); 40 | 41 | resForDecrease.forEach((post, index, parent) => { 42 | post.pid = index; 43 | }); 44 | 45 | if (new DOM("#Thread").children.length < resForIncrease.length) { 46 | for (let i = new DOM("#Thread").children.length; i < resForIncrease.length; i++) { 47 | let post = new Component.Thread.Post(resForIncrease[i].uid, TID, resForIncrease[i].pid, "", resForIncrease[i].content, new Date(resForIncrease[i].createdAt).toLocaleString(), base.user.uid == resForIncrease[i].uid && i !== 0); 48 | if (post.querySelector(`*[Data-Component="${Component.Thread.Post.UUIDS.MENU.ROOT}"]`)) { 49 | setTimeout(() => { 50 | componentHandler.upgradeElement(post.querySelector(`*[Data-Component="${Component.Thread.Post.UUIDS.MENU.ROOT}"]`)); 51 | }); 52 | } 53 | 54 | base.Database.get(base.Database.ONCE, "users/" + post.uid, (userRes) => { 55 | post.querySelector('*[Data-Component="Thread_Post_Header_Actor"]').textContent = userRes.userName; 56 | }); 57 | 58 | URL.filter(post.querySelector('*[Data-Component="Thread_Post_Content"]').textContent).forEach((urlString) => { 59 | post.querySelector('*[Data-Component="Thread_Post_Content"]').innerHTML = post.querySelector('*[Data-Component="Thread_Post_Content"]').innerHTML.replace(urlString, `${urlString}`); 60 | }); 61 | 62 | new DOM("#Thread").appendChild(post); 63 | } 64 | } else { 65 | new DOM('@Div[Data-Component="Thread_Post"]').forEach((post) => { 66 | if (!resForDecrease[post.dataset.pid]) post.remove(); 67 | }); 68 | 69 | new DOM('@Div[Data-Component="Thread_Post-Mine"]').forEach((post) => { 70 | if (!resForDecrease[post.dataset.pid]) post.remove(); 71 | }); 72 | } 73 | }); 74 | 75 | 76 | 77 | new DOM("#FlowPanel_Btns_CreatePost").addEventListener("click", () => { 78 | doc.querySelector("#Dialogs_Thread_Poster").showModal(); 79 | 80 | new DOM("#FlowPanel_Btns_CreatePost").setAttribute("Disabled", ""); 81 | }); 82 | 83 | new DOM("#FlowPanel_Btns_Scroller").addEventListener("click", () => { 84 | 85 | }); 86 | 87 | new DOM("#FlowPanel_Btns_ShowThreadInfo").addEventListener("click", () => { 88 | doc.querySelector("#Dialogs_Thread_InfoViewer").showModal(); 89 | }); 90 | }); -------------------------------------------------------------------------------- /Thread/Viewer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple Thread == スレッドビューア 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 |
    47 |
    48 | 49 |
    50 |
    51 | 52 |
    53 |
    54 | 57 | 58 | 61 |
    62 | 63 |
    64 | 67 |
    68 |
    69 | 70 | -------------------------------------------------------------------------------- /Thread/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple Thread == スレッド一覧 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 39 | 40 |
    41 |
    42 |
    43 | スレッド検索 44 | 管理中のスレッド 45 | 46 | 47 | refresh 48 | 49 | 50 |
    Refresh threadlist
    51 |
    52 | 53 | 54 | 55 | 67 | 68 | 76 |
    77 |
    78 | 79 | -------------------------------------------------------------------------------- /Top/Top.css: -------------------------------------------------------------------------------- 1 | #Top_Logo { 2 | Width: 100%; 3 | 4 | Margin-Bottom: 5vmax; 5 | } -------------------------------------------------------------------------------- /Top/Top.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GenbuProject/SimpleThread/60bfc1ba5756be70ca03dad1a7416b56442496ae/Top/Top.js -------------------------------------------------------------------------------- /Top/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple Thread == トップページ 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 39 | 40 |
    41 |
    42 | 43 | 44 |

    45 | Simple Threadへようこそ! 46 | 当サービスでは "Be Simple, Be Free" をモットーに、他サービスと比べ自由に話せるスレ板を提供しますΣd(´・ω・`) 47 |

    48 |
    49 |
    50 | 51 | -------------------------------------------------------------------------------- /assets/classes/DBLoader.js: -------------------------------------------------------------------------------- 1 | class DBLoader extends FirebasePlus { 2 | constructor (configUrl = "assets/firebase.json", onload = () => {}) { 3 | let cfgLoader = new JSONLoader(); 4 | 5 | super(cfgLoader.load(configUrl), onload); 6 | } 7 | 8 | 9 | 10 | delete () { 11 | this.reauth().then(() => { 12 | this.Database.get(this.Database.ONCE, "threads/", res => { 13 | res.forEach((thread, threadIndex) => { 14 | if (thread.data) { 15 | for (let uid in thread.jobs.Owner) { 16 | if (uid === this.user.uid) { 17 | this.Database.delete(`threads/${threadIndex}/`); 18 | return; 19 | } 20 | } 21 | 22 | thread.data.forEach((post, postIndex) => { 23 | if (post.uid == this.user.uid) this.Database.delete(`threads/${threadIndex}/data/${postIndex}/`); 24 | }); 25 | } 26 | }); 27 | }); 28 | 29 | this.accessToken = "", 30 | this.idToken = "", 31 | this.signInType = "", 32 | this.signInScope = []; 33 | 34 | this.Database.delete("users/" + this.user.uid); 35 | this.user.delete(); 36 | 37 | location.reload(); 38 | }); 39 | } 40 | } -------------------------------------------------------------------------------- /assets/classes/Encrypter.js: -------------------------------------------------------------------------------- 1 | class Encrypter { 2 | /** 3 | * 文字列をHash(SHA-512)で暗号化する 4 | * @memberof Encrypter 5 | * @static 6 | * 7 | * @param {String} [text=""] 8 | * @param {String} [type="HEX"] 9 | * @returns {String} 10 | */ 11 | static encrypt (text = "", type = "HEX") { 12 | let encrypter = new jsSHA("SHA-512", "TEXT"); 13 | encrypter.update(text); 14 | 15 | return encrypter.getHash(type); 16 | } 17 | } -------------------------------------------------------------------------------- /assets/classes/FileLoader.js: -------------------------------------------------------------------------------- 1 | class FileLoader { 2 | constructor () {} 3 | 4 | 5 | 6 | /** 7 | * 指定されたurlからファイルを読み込む 8 | * @memberof FileLoader 9 | * 10 | * @param {String} url 11 | */ 12 | load (url) { 13 | try { 14 | let reader = new XMLHttpRequest(); 15 | reader.open("GET", url || location.href, false); 16 | reader.send(null); 17 | 18 | this.currentData = reader.response; 19 | } catch (error) { 20 | this.currentData = null; 21 | console.error(error); 22 | } 23 | 24 | return this.currentData; 25 | } 26 | }; Object.defineProperties(FileLoader.prototype, { 27 | currentData: { value: null, configurable: true, writable: true, enumerable: true } 28 | }); -------------------------------------------------------------------------------- /assets/classes/JSONLoader.js: -------------------------------------------------------------------------------- 1 | class JSONLoader extends FileLoader { 2 | constructor () { super() } 3 | 4 | 5 | 6 | /** 7 | * 指定されたurlからJSONデータを読み込む 8 | * @memberof JSONLoader 9 | * 10 | * @param {String} url 11 | */ 12 | load (url) { 13 | this.currentData = JSON.parse(super.load(url)); 14 | return this.currentData; 15 | } 16 | } -------------------------------------------------------------------------------- /assets/classes/LangLoader.js: -------------------------------------------------------------------------------- 1 | class LangLoader extends JSONLoader { 2 | constructor () { 3 | super(); 4 | 5 | localStorage.getItem("com.GenbuProject.SimpleThread.currentLang") || localStorage.setItem("com.GenbuProject.SimpleThread.currentLang", "ja_JP"); 6 | } 7 | 8 | /** 9 | * 指定されたurlからローカライズデータを読み込む 10 | * @memberof LangLoader 11 | * 12 | * @param {String} lang 13 | */ 14 | load (lang) { 15 | return super.load(`/SimpleThread/assets/locales/${lang}.json`); 16 | } 17 | 18 | /** 19 | * ローカライズ処理を実行 20 | * @memberof LangLoader 21 | * 22 | * @param {Window} windowObj 23 | */ 24 | apply (windowObj) { 25 | let localizedElems = windowObj.document.querySelectorAll('*[Data-Locales]'); 26 | 27 | localizedElems.forEach((elem) => { 28 | if (this.currentData[elem.dataset.locales]) { 29 | if (Array.isArray(this.currentData[elem.dataset.locales])) { 30 | elem.firstChild.data = this.currentData[elem.dataset.locales].join("\n"); 31 | } else { 32 | elem.firstChild.data = this.currentData[elem.dataset.locales]; 33 | } 34 | } 35 | }); 36 | } 37 | 38 | /** 39 | * 指定の要素に対しローカライズ処理を実行 40 | * @memberof LangLoader 41 | * 42 | * @param {HTMLElement} elem 43 | */ 44 | applyToElement (elem) { 45 | if (this.currentData[elem.dataset.locales]) { 46 | if (Array.isArray(this.currentData[elem.dataset.locales])) { 47 | elem.firstChild.data = this.currentData[elem.dataset.locales].join("\n"); 48 | } else { 49 | elem.firstChild.data = this.currentData[elem.dataset.locales]; 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /assets/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiKey": "AIzaSyA62uPkN6WNV41oWWzOdiITMbBF9RDYOhM", 3 | "authDomain": "simple-thread.firebaseapp.com", 4 | "databaseURL": "https://simple-thread.firebaseio.com", 5 | "projectId": "simple-thread", 6 | "storageBucket": "simple-thread.appspot.com", 7 | "messagingSenderId": 646527306803, 8 | 9 | "client_id": "646527306803-1ecjnmee3pddu6tkakrevs7lkka1daql.apps.googleusercontent.com", 10 | 11 | "scope": [ 12 | "https://www.googleapis.com/auth/plus.login", 13 | "https://www.googleapis.com/auth/drive.readonly", 14 | "https://www.googleapis.com/auth/photos" 15 | ] 16 | } -------------------------------------------------------------------------------- /assets/images/Back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GenbuProject/SimpleThread/60bfc1ba5756be70ca03dad1a7416b56442496ae/assets/images/Back.jpg -------------------------------------------------------------------------------- /assets/images/Icon-Info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GenbuProject/SimpleThread/60bfc1ba5756be70ca03dad1a7416b56442496ae/assets/images/Icon-Info.png -------------------------------------------------------------------------------- /assets/images/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GenbuProject/SimpleThread/60bfc1ba5756be70ca03dad1a7416b56442496ae/assets/images/Logo.png -------------------------------------------------------------------------------- /assets/includes/Component.html: -------------------------------------------------------------------------------- 1 |
    2 | 7 |
    8 | 9 |
    10 |
    11 | 12 | ${0} 13 |
    14 |
    15 | 16 |
    17 | 18 | 19 | subject 20 | ${1} 21 | ${2} 22 | 23 | 24 | 25 |
    26 | 27 | subject 28 | ${1} 29 | ${2} 30 | 31 | 32 | 33 | 36 | 37 | 40 | 41 |
    42 | 43 | 44 | 45 | subject 46 | ${1} 47 | ${2} 48 | 49 | 50 | 51 | lock 52 | 53 | 54 |
    55 | 56 |
    57 |
    58 |
    59 | 60 | ${2} 61 |
    62 | 63 | ${4} 64 |
    65 | 66 |
    ${3}
    67 |
    68 |
    69 | 70 |
    71 |
    72 | 73 | ${2} 74 |
    75 | 76 | ${4} 77 | 78 | 81 | 82 |
      83 |
    • 投稿を削除
    • 84 |
    85 |
    86 | 87 |
    ${3}
    88 |
    89 |
    90 |
    -------------------------------------------------------------------------------- /assets/includes/Component.js: -------------------------------------------------------------------------------- 1 | class Component { 2 | /** 3 | * コンポーネントへの参照 4 | * @returns {HTMLBodyElement} 5 | */ 6 | static get doc () { 7 | let doc = new DOM("Body"); 8 | 9 | try { 10 | doc.innerHTML = DOM.xhr({ 11 | type: "GET", 12 | url: "/SimpleThread/assets/includes/Component.html", 13 | doesSync: false 14 | }).response; 15 | } catch (error) {} 16 | 17 | return doc; 18 | } 19 | 20 | 21 | 22 | constructor (componentName = "") { 23 | try { 24 | let component = document.importNode(Component.doc.querySelector(`*[Data-Component="${componentName}"]`), true); 25 | 26 | let componentWrapper = new DOM("ComponentWrapper"); 27 | componentWrapper.appendChild(component); 28 | 29 | componentWrapper.firstElementChild.outerHTML = (() => { 30 | let content = componentWrapper.firstElementChild.outerHTML; 31 | 32 | for (let i = 0; i < arguments.length + 1; i++) { 33 | content = content.replace(new RegExp("\\$\\{" + i + "\\}", "g"), arguments[i + 1]); 34 | } 35 | 36 | return content; 37 | })(); 38 | 39 | return componentWrapper.firstElementChild; 40 | } catch (error) { 41 | console.error(error); 42 | } 43 | } 44 | 45 | 46 | 47 | static get Styles () { 48 | return { 49 | ProfilePhotoManager: class ProfilePhotoManager { 50 | static get UUIDS () { 51 | return { 52 | ROOT: 'Styles_ProfilePhoto--Manager' 53 | } 54 | } 55 | 56 | 57 | 58 | constructor (uid = "", photoUrl = "") { 59 | return new Component(ProfilePhotoManager.UUIDS.ROOT, uid, photoUrl); 60 | } 61 | } 62 | } 63 | } 64 | 65 | static get Dialogs () { 66 | return { 67 | Profile: { 68 | InfoViewer: { 69 | Links: { 70 | Link: class Link { 71 | static get UUIDS () { 72 | return { 73 | ROOT: 'Dialogs_Profile_InfoViewer_Content_Info_Links_Link', 74 | ICON: 'Dialogs_Profile_InfoViewer_Content_Info_Links_Link_Icon', 75 | } 76 | } 77 | 78 | 79 | 80 | constructor (urlTitle = "", url = "") { 81 | let self = new Component(Link.UUIDS.ROOT, urlTitle, url); 82 | self.querySelector(`Img[Data-Component="${Link.UUIDS.ICON}"]`).src = `${new URL(url).origin}/favicon.ico` || `${locaion.origin}/favicon.ico`; 83 | 84 | return self; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | static get Threadlist () { 94 | return { 95 | Thread: class Thread { 96 | static get UUIDS () { 97 | return { 98 | ROOT: 'Threadlist_Thread', 99 | 100 | ADMIN: { 101 | ROOT: 'Threadlist_Thread-Admin', 102 | 103 | MENU: { 104 | ROOT: 'Threadlist_Thread-Admin_Menu', 105 | 106 | EDIT: 'Threadlist_Thread-Admin_Menu_MenuItem-Edit', 107 | DELETE: 'Threadlist_Thread-Admin_Menu_MenuItem-Delete' 108 | } 109 | }, 110 | 111 | SECURED: 'Threadlist_Thread-Secured' 112 | } 113 | } 114 | 115 | 116 | 117 | constructor (tid = "", title = "", overview = "", isAdmin = false, isSecured = false) { 118 | let self = new Component(isAdmin ? Thread.UUIDS.ADMIN.ROOT : isSecured ? Thread.UUIDS.SECURED : Thread.UUIDS.ROOT, tid, title, overview, new DOM.Randomizer().generate(16)); 119 | if (self.querySelector(`*[Data-Component="${Thread.UUIDS.ADMIN.MENU.ROOT}"]`)) { 120 | self.querySelector(`*[Data-Component="${Thread.UUIDS.ADMIN.MENU.EDIT}"]`).addEventListener("click", () => { 121 | parent.document.querySelector("#Dialogs_Thread_InfoInputter_Btns_Edit").removeAttribute("Disabled"), 122 | parent.document.querySelector("#Dialogs_Thread_InfoInputter_Btns_Create").setAttribute("Disabled", ""); 123 | 124 | parent.document.querySelector("#Dialogs_Thread_InfoInputter").showModal(); 125 | }); 126 | 127 | self.querySelector(`*[Data-Component="${Thread.UUIDS.ADMIN.MENU.DELETE}"]`).addEventListener("click", () => { 128 | parent.document.querySelector("#Dialogs_Thread_DeleteConfirmer").showModal(); 129 | }); 130 | } 131 | 132 | return self; 133 | } 134 | } 135 | } 136 | } 137 | 138 | static get Thread () { 139 | return { 140 | Post: class Post { 141 | static get UUIDS () { 142 | return { 143 | ROOT: 'Thread_Post', 144 | MINE: 'Thread_Post-Mine', 145 | 146 | HEADER: { 147 | ROOT: 'Thread_Post_Header', 148 | 149 | PHOTO: 'Thread_Post_Header_ActorPhoto', 150 | NAME: 'Thread_Post_Header_Actor', 151 | CREATED: 'Thread_Post_Header_CreatedAt' 152 | }, 153 | 154 | MENU: { 155 | ROOT: 'Thread_Post_Header_Menu', 156 | DELETE: 'Thread_Post_Header_Menu_MenuItem-Delete' 157 | } 158 | } 159 | } 160 | 161 | 162 | 163 | constructor (uid = "", tid = "", pid = "", userName = "", content = "", createdAt = new Date().toLocaleString(), isMine = false) { 164 | let self = new Component(!isMine ? Post.UUIDS.ROOT : Post.UUIDS.MINE, pid, uid, userName, content, createdAt, new DOM.Randomizer().generate(16)); 165 | self.uid = uid, 166 | self.tid = tid, 167 | self.pid = pid; 168 | 169 | self.querySelector(`*[Data-Component="${Post.UUIDS.HEADER.PHOTO}"]`).addEventListener("click", () => { 170 | parent.document.querySelector("#Dialogs_Profile_InfoViewer_UID").value = self.uid; 171 | parent.document.querySelector("#Dialogs_Profile_InfoViewer").showModal(); 172 | }); 173 | 174 | if (self.querySelector(`*[Data-Component="${Post.UUIDS.MENU.ROOT}"]`)) { 175 | self.querySelector(`*[Data-Component="${Post.UUIDS.MENU.DELETE}"]`).addEventListener("click", () => { 176 | base.Database.delete(`threads/${self.tid}/data/${self.pid}/`); 177 | }); 178 | } 179 | 180 | return self; 181 | } 182 | } 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /assets/includes/Core.css: -------------------------------------------------------------------------------- 1 | Body { 2 | Background: URL("/SimpleThread/assets/images/Back.jpg"); 3 | } 4 | 5 | Main { 6 | Padding: 4vmin; 7 | } 8 | 9 | A:Not([Href]) { 10 | Cursor: Pointer; 11 | } 12 | 13 | *[Data-Locales] { 14 | White-Space: Pre-Wrap; 15 | } 16 | 17 | 18 | 19 | *[UUID="ProfilePhoto"] { 20 | Width: Auto; 21 | 22 | Background: Transparent Center / Cover; 23 | Border-Radius: 100%; 24 | } 25 | 26 | *[UUID="ProfilePhoto"]::Before { 27 | Content: ""; 28 | 29 | Display: Block; 30 | Padding-Top: 100%; 31 | } 32 | 33 | *[UUID="ProfilePhoto-Btn"] { 34 | Background: Transparent Center / Cover; 35 | } 36 | 37 | *[UUID="ProfilePhoto-Btn"][Disabled] { 38 | Display: None; 39 | } -------------------------------------------------------------------------------- /assets/includes/Core.js: -------------------------------------------------------------------------------- 1 | window.base = parent.base || {}; 2 | window.terminal = parent.terminal || {}; 3 | window.locales = parent.locales; 4 | window.doc = parent.document; 5 | 6 | 7 | 8 | try { 9 | terminal.postMessage({ code: "Code-Connected" }); 10 | terminal.postMessage({ code: "Code-RequestHasLogined" }); 11 | } catch (error) { 12 | location.href = "/SimpleThread/Error/403.10/"; 13 | } 14 | 15 | window.addEventListener("DOMContentLoaded", () => { 16 | locales.apply(this); 17 | 18 | new DOM("@Main").forEach(elem => { 19 | let classes = navigator.isMobile() ? 20 | ["mdl-cell", "mdl-cell--12-col", "mdl-shadow--4dp", "mdl-color--white", "mdl-color-text--grey-800"] : 21 | ["mdl-cell", "mdl-cell--2-offset", "mdl-cell--8-col", "mdl-shadow--4dp", "mdl-color--white", "mdl-color-text--grey-800"]; 22 | 23 | classes.forEach(className => { 24 | elem.classList.add(className); 25 | }); 26 | }); 27 | 28 | new DOM('@A[Href]:Not([Target]):Not([Href^="javascript:"])').forEach((elem) => { 29 | elem.addEventListener("click", (event) => { 30 | event.preventDefault(); 31 | 32 | parent.document.querySelector("IFrame.mdl-layout__content").src = elem.href; 33 | }); 34 | }); 35 | 36 | parent.document.querySelector("IFrame#Page").contentWindow.addEventListener("beforeunload", () => { 37 | parent.document.querySelectorAll("Dialog[Open]").forEach((dialog) => { 38 | dialog.close(); 39 | }); 40 | }); 41 | }); 42 | 43 | window.addEventListener("DOMNodeInserted", (event) => { 44 | if (event.target.nodeName != "#text" && event.relatedNode.dataset && event.relatedNode.dataset.locales) { 45 | locales.applyToElement(event.relatedNode); 46 | } 47 | }); -------------------------------------------------------------------------------- /assets/includes/dialog-polyfill.css: -------------------------------------------------------------------------------- 1 | dialog { 2 | position: absolute; 3 | left: 0; right: 0; 4 | width: -moz-fit-content; 5 | width: -webkit-fit-content; 6 | width: fit-content; 7 | height: -moz-fit-content; 8 | height: -webkit-fit-content; 9 | height: fit-content; 10 | margin: auto; 11 | border: solid; 12 | padding: 1em; 13 | background: white; 14 | color: black; 15 | display: block; 16 | } 17 | 18 | dialog:not([open]) { 19 | display: none; 20 | } 21 | 22 | dialog + .backdrop { 23 | position: fixed; 24 | top: 0; right: 0; bottom: 0; left: 0; 25 | background: rgba(0,0,0,0.1); 26 | } 27 | 28 | ._dialog_overlay { 29 | position: fixed; 30 | top: 0; right: 0; bottom: 0; left: 0; 31 | } 32 | 33 | dialog.fixed { 34 | position: fixed; 35 | top: 50%; 36 | transform: translate(0, -50%); 37 | } -------------------------------------------------------------------------------- /assets/includes/dialog-polyfill.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // nb. This is for IE10 and lower _only_. 4 | var supportCustomEvent = window.CustomEvent; 5 | if (!supportCustomEvent || typeof supportCustomEvent === 'object') { 6 | supportCustomEvent = function CustomEvent(event, x) { 7 | x = x || {}; 8 | var ev = document.createEvent('CustomEvent'); 9 | ev.initCustomEvent(event, !!x.bubbles, !!x.cancelable, x.detail || null); 10 | return ev; 11 | }; 12 | supportCustomEvent.prototype = window.Event.prototype; 13 | } 14 | 15 | /** 16 | * @param {Element} el to check for stacking context 17 | * @return {boolean} whether this el or its parents creates a stacking context 18 | */ 19 | function createsStackingContext(el) { 20 | while (el && el !== document.body) { 21 | var s = window.getComputedStyle(el); 22 | var invalid = function(k, ok) { 23 | return !(s[k] === undefined || s[k] === ok); 24 | } 25 | if (s.opacity < 1 || 26 | invalid('zIndex', 'auto') || 27 | invalid('transform', 'none') || 28 | invalid('mixBlendMode', 'normal') || 29 | invalid('filter', 'none') || 30 | invalid('perspective', 'none') || 31 | s['isolation'] === 'isolate' || 32 | s.position === 'fixed' || 33 | s.webkitOverflowScrolling === 'touch') { 34 | return true; 35 | } 36 | el = el.parentElement; 37 | } 38 | return false; 39 | } 40 | 41 | /** 42 | * Finds the nearest from the passed element. 43 | * 44 | * @param {Element} el to search from 45 | * @return {HTMLDialogElement} dialog found 46 | */ 47 | function findNearestDialog(el) { 48 | while (el) { 49 | if (el.localName === 'dialog') { 50 | return /** @type {HTMLDialogElement} */ (el); 51 | } 52 | el = el.parentElement; 53 | } 54 | return null; 55 | } 56 | 57 | /** 58 | * Blur the specified element, as long as it's not the HTML body element. 59 | * This works around an IE9/10 bug - blurring the body causes Windows to 60 | * blur the whole application. 61 | * 62 | * @param {Element} el to blur 63 | */ 64 | function safeBlur(el) { 65 | if (el && el.blur && el !== document.body) { 66 | el.blur(); 67 | } 68 | } 69 | 70 | /** 71 | * @param {!NodeList} nodeList to search 72 | * @param {Node} node to find 73 | * @return {boolean} whether node is inside nodeList 74 | */ 75 | function inNodeList(nodeList, node) { 76 | for (var i = 0; i < nodeList.length; ++i) { 77 | if (nodeList[i] === node) { 78 | return true; 79 | } 80 | } 81 | return false; 82 | } 83 | 84 | /** 85 | * @param {HTMLFormElement} el to check 86 | * @return {boolean} whether this form has method="dialog" 87 | */ 88 | function isFormMethodDialog(el) { 89 | if (!el || !el.hasAttribute('method')) { 90 | return false; 91 | } 92 | return el.getAttribute('method').toLowerCase() === 'dialog'; 93 | } 94 | 95 | /** 96 | * @param {!HTMLDialogElement} dialog to upgrade 97 | * @constructor 98 | */ 99 | function dialogPolyfillInfo(dialog) { 100 | this.dialog_ = dialog; 101 | this.replacedStyleTop_ = false; 102 | this.openAsModal_ = false; 103 | 104 | // Set a11y role. Browsers that support dialog implicitly know this already. 105 | if (!dialog.hasAttribute('role')) { 106 | dialog.setAttribute('role', 'dialog'); 107 | } 108 | 109 | dialog.show = this.show.bind(this); 110 | dialog.showModal = this.showModal.bind(this); 111 | dialog.close = this.close.bind(this); 112 | 113 | if (!('returnValue' in dialog)) { 114 | dialog.returnValue = ''; 115 | } 116 | 117 | if ('MutationObserver' in window) { 118 | var mo = new MutationObserver(this.maybeHideModal.bind(this)); 119 | mo.observe(dialog, {attributes: true, attributeFilter: ['open']}); 120 | } else { 121 | // IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also 122 | // seem to fire even if the element was removed as part of a parent removal. Use the removed 123 | // events to force downgrade (useful if removed/immediately added). 124 | var removed = false; 125 | var cb = function() { 126 | removed ? this.downgradeModal() : this.maybeHideModal(); 127 | removed = false; 128 | }.bind(this); 129 | var timeout; 130 | var delayModel = function(ev) { 131 | if (ev.target !== dialog) { return; } // not for a child element 132 | var cand = 'DOMNodeRemoved'; 133 | removed |= (ev.type.substr(0, cand.length) === cand); 134 | window.clearTimeout(timeout); 135 | timeout = window.setTimeout(cb, 0); 136 | }; 137 | ['DOMAttrModified', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument'].forEach(function(name) { 138 | dialog.addEventListener(name, delayModel); 139 | }); 140 | } 141 | // Note that the DOM is observed inside DialogManager while any dialog 142 | // is being displayed as a modal, to catch modal removal from the DOM. 143 | 144 | Object.defineProperty(dialog, 'open', { 145 | set: this.setOpen.bind(this), 146 | get: dialog.hasAttribute.bind(dialog, 'open') 147 | }); 148 | 149 | this.backdrop_ = document.createElement('div'); 150 | this.backdrop_.className = 'backdrop'; 151 | this.backdrop_.addEventListener('click', this.backdropClick_.bind(this)); 152 | } 153 | 154 | dialogPolyfillInfo.prototype = { 155 | 156 | get dialog() { 157 | return this.dialog_; 158 | }, 159 | 160 | /** 161 | * Maybe remove this dialog from the modal top layer. This is called when 162 | * a modal dialog may no longer be tenable, e.g., when the dialog is no 163 | * longer open or is no longer part of the DOM. 164 | */ 165 | maybeHideModal: function() { 166 | if (this.dialog_.hasAttribute('open') && document.body.contains(this.dialog_)) { return; } 167 | this.downgradeModal(); 168 | }, 169 | 170 | /** 171 | * Remove this dialog from the modal top layer, leaving it as a non-modal. 172 | */ 173 | downgradeModal: function() { 174 | if (!this.openAsModal_) { return; } 175 | this.openAsModal_ = false; 176 | this.dialog_.style.zIndex = ''; 177 | 178 | // This won't match the native exactly because if the user set top on a centered 179 | // polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's 180 | // possible to polyfill this perfectly. 181 | if (this.replacedStyleTop_) { 182 | this.dialog_.style.top = ''; 183 | this.replacedStyleTop_ = false; 184 | } 185 | 186 | // Clear the backdrop and remove from the manager. 187 | this.backdrop_.parentNode && this.backdrop_.parentNode.removeChild(this.backdrop_); 188 | dialogPolyfill.dm.removeDialog(this); 189 | }, 190 | 191 | /** 192 | * @param {boolean} value whether to open or close this dialog 193 | */ 194 | setOpen: function(value) { 195 | if (value) { 196 | this.dialog_.hasAttribute('open') || this.dialog_.setAttribute('open', ''); 197 | } else { 198 | this.dialog_.removeAttribute('open'); 199 | this.maybeHideModal(); // nb. redundant with MutationObserver 200 | } 201 | }, 202 | 203 | /** 204 | * Handles clicks on the fake .backdrop element, redirecting them as if 205 | * they were on the dialog itself. 206 | * 207 | * @param {!Event} e to redirect 208 | */ 209 | backdropClick_: function(e) { 210 | if (!this.dialog_.hasAttribute('tabindex')) { 211 | // Clicking on the backdrop should move the implicit cursor, even if dialog cannot be 212 | // focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this 213 | // would not be needed - clicks would move the implicit cursor there. 214 | var fake = document.createElement('div'); 215 | this.dialog_.insertBefore(fake, this.dialog_.firstChild); 216 | fake.tabIndex = -1; 217 | fake.focus(); 218 | this.dialog_.removeChild(fake); 219 | } else { 220 | this.dialog_.focus(); 221 | } 222 | 223 | var redirectedEvent = document.createEvent('MouseEvents'); 224 | redirectedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, window, 225 | e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, 226 | e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget); 227 | this.dialog_.dispatchEvent(redirectedEvent); 228 | e.stopPropagation(); 229 | }, 230 | 231 | /** 232 | * Focuses on the first focusable element within the dialog. This will always blur the current 233 | * focus, even if nothing within the dialog is found. 234 | */ 235 | focus_: function() { 236 | // Find element with `autofocus` attribute, or fall back to the first form/tabindex control. 237 | var target = this.dialog_.querySelector('[autofocus]:not([disabled])'); 238 | if (!target && this.dialog_.tabIndex >= 0) { 239 | target = this.dialog_; 240 | } 241 | if (!target) { 242 | // Note that this is 'any focusable area'. This list is probably not exhaustive, but the 243 | // alternative involves stepping through and trying to focus everything. 244 | var opts = ['button', 'input', 'keygen', 'select', 'textarea']; 245 | var query = opts.map(function(el) { 246 | return el + ':not([disabled])'; 247 | }); 248 | // TODO(samthor): tabindex values that are not numeric are not focusable. 249 | query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled 250 | target = this.dialog_.querySelector(query.join(', ')); 251 | } 252 | safeBlur(document.activeElement); 253 | target && target.focus(); 254 | }, 255 | 256 | /** 257 | * Sets the zIndex for the backdrop and dialog. 258 | * 259 | * @param {number} dialogZ 260 | * @param {number} backdropZ 261 | */ 262 | updateZIndex: function(dialogZ, backdropZ) { 263 | if (dialogZ < backdropZ) { 264 | throw new Error('dialogZ should never be < backdropZ'); 265 | } 266 | this.dialog_.style.zIndex = dialogZ; 267 | this.backdrop_.style.zIndex = backdropZ; 268 | }, 269 | 270 | /** 271 | * Shows the dialog. If the dialog is already open, this does nothing. 272 | */ 273 | show: function() { 274 | if (!this.dialog_.open) { 275 | this.setOpen(true); 276 | this.focus_(); 277 | } 278 | }, 279 | 280 | /** 281 | * Show this dialog modally. 282 | */ 283 | showModal: function() { 284 | if (this.dialog_.hasAttribute('open')) { 285 | throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.'); 286 | } 287 | if (!document.body.contains(this.dialog_)) { 288 | throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.'); 289 | } 290 | if (!dialogPolyfill.dm.pushDialog(this)) { 291 | throw new Error('Failed to execute \'showModal\' on dialog: There are too many open modal dialogs.'); 292 | } 293 | 294 | if (createsStackingContext(this.dialog_.parentElement)) { 295 | console.warn('A dialog is being shown inside a stacking context. ' + 296 | 'This may cause it to be unusable. For more information, see this link: ' + 297 | 'https://github.com/GoogleChrome/dialog-polyfill/#stacking-context'); 298 | } 299 | 300 | this.setOpen(true); 301 | this.openAsModal_ = true; 302 | 303 | // Optionally center vertically, relative to the current viewport. 304 | if (dialogPolyfill.needsCentering(this.dialog_)) { 305 | dialogPolyfill.reposition(this.dialog_); 306 | this.replacedStyleTop_ = true; 307 | } else { 308 | this.replacedStyleTop_ = false; 309 | } 310 | 311 | // Insert backdrop. 312 | this.dialog_.parentNode.insertBefore(this.backdrop_, this.dialog_.nextSibling); 313 | 314 | // Focus on whatever inside the dialog. 315 | this.focus_(); 316 | }, 317 | 318 | /** 319 | * Closes this HTMLDialogElement. This is optional vs clearing the open 320 | * attribute, however this fires a 'close' event. 321 | * 322 | * @param {string=} opt_returnValue to use as the returnValue 323 | */ 324 | close: function(opt_returnValue) { 325 | if (!this.dialog_.hasAttribute('open')) { 326 | throw new Error('Failed to execute \'close\' on dialog: The element does not have an \'open\' attribute, and therefore cannot be closed.'); 327 | } 328 | this.setOpen(false); 329 | 330 | // Leave returnValue untouched in case it was set directly on the element 331 | if (opt_returnValue !== undefined) { 332 | this.dialog_.returnValue = opt_returnValue; 333 | } 334 | 335 | // Triggering "close" event for any attached listeners on the . 336 | var closeEvent = new supportCustomEvent('close', { 337 | bubbles: false, 338 | cancelable: false 339 | }); 340 | this.dialog_.dispatchEvent(closeEvent); 341 | } 342 | 343 | }; 344 | 345 | var dialogPolyfill = {}; 346 | 347 | dialogPolyfill.reposition = function(element) { 348 | var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 349 | var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2; 350 | element.style.top = Math.max(scrollTop, topValue) + 'px'; 351 | }; 352 | 353 | dialogPolyfill.isInlinePositionSetByStylesheet = function(element) { 354 | for (var i = 0; i < document.styleSheets.length; ++i) { 355 | var styleSheet = document.styleSheets[i]; 356 | var cssRules = null; 357 | // Some browsers throw on cssRules. 358 | try { 359 | cssRules = styleSheet.cssRules; 360 | } catch (e) {} 361 | if (!cssRules) { continue; } 362 | for (var j = 0; j < cssRules.length; ++j) { 363 | var rule = cssRules[j]; 364 | var selectedNodes = null; 365 | // Ignore errors on invalid selector texts. 366 | try { 367 | selectedNodes = document.querySelectorAll(rule.selectorText); 368 | } catch(e) {} 369 | if (!selectedNodes || !inNodeList(selectedNodes, element)) { 370 | continue; 371 | } 372 | var cssTop = rule.style.getPropertyValue('top'); 373 | var cssBottom = rule.style.getPropertyValue('bottom'); 374 | if ((cssTop && cssTop !== 'auto') || (cssBottom && cssBottom !== 'auto')) { 375 | return true; 376 | } 377 | } 378 | } 379 | return false; 380 | }; 381 | 382 | dialogPolyfill.needsCentering = function(dialog) { 383 | var computedStyle = window.getComputedStyle(dialog); 384 | if (computedStyle.position !== 'absolute') { 385 | return false; 386 | } 387 | 388 | // We must determine whether the top/bottom specified value is non-auto. In 389 | // WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but 390 | // Firefox returns the used value. So we do this crazy thing instead: check 391 | // the inline style and then go through CSS rules. 392 | if ((dialog.style.top !== 'auto' && dialog.style.top !== '') || 393 | (dialog.style.bottom !== 'auto' && dialog.style.bottom !== '')) { 394 | return false; 395 | } 396 | return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog); 397 | }; 398 | 399 | /** 400 | * @param {!Element} element to force upgrade 401 | */ 402 | dialogPolyfill.forceRegisterDialog = function(element) { 403 | if (window.HTMLDialogElement || element.showModal) { 404 | console.warn('This browser already supports , the polyfill ' + 405 | 'may not work correctly', element); 406 | } 407 | if (element.localName !== 'dialog') { 408 | throw new Error('Failed to register dialog: The element is not a dialog.'); 409 | } 410 | new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ (element)); 411 | }; 412 | 413 | /** 414 | * @param {!Element} element to upgrade, if necessary 415 | */ 416 | dialogPolyfill.registerDialog = function(element) { 417 | if (!element.showModal) { 418 | dialogPolyfill.forceRegisterDialog(element); 419 | } 420 | }; 421 | 422 | /** 423 | * @constructor 424 | */ 425 | dialogPolyfill.DialogManager = function() { 426 | /** @type {!Array} */ 427 | this.pendingDialogStack = []; 428 | 429 | var checkDOM = this.checkDOM_.bind(this); 430 | 431 | // The overlay is used to simulate how a modal dialog blocks the document. 432 | // The blocking dialog is positioned on top of the overlay, and the rest of 433 | // the dialogs on the pending dialog stack are positioned below it. In the 434 | // actual implementation, the modal dialog stacking is controlled by the 435 | // top layer, where z-index has no effect. 436 | this.overlay = document.createElement('div'); 437 | this.overlay.className = '_dialog_overlay'; 438 | this.overlay.addEventListener('click', function(e) { 439 | this.forwardTab_ = undefined; 440 | e.stopPropagation(); 441 | checkDOM([]); // sanity-check DOM 442 | }.bind(this)); 443 | 444 | this.handleKey_ = this.handleKey_.bind(this); 445 | this.handleFocus_ = this.handleFocus_.bind(this); 446 | 447 | this.zIndexLow_ = 100000; 448 | this.zIndexHigh_ = 100000 + 150; 449 | 450 | this.forwardTab_ = undefined; 451 | 452 | if ('MutationObserver' in window) { 453 | this.mo_ = new MutationObserver(function(records) { 454 | var removed = []; 455 | records.forEach(function(rec) { 456 | for (var i = 0, c; c = rec.removedNodes[i]; ++i) { 457 | if (!(c instanceof Element)) { 458 | continue; 459 | } else if (c.localName === 'dialog') { 460 | removed.push(c); 461 | } 462 | removed = removed.concat(c.querySelectorAll('dialog')); 463 | } 464 | }); 465 | removed.length && checkDOM(removed); 466 | }); 467 | } 468 | }; 469 | 470 | /** 471 | * Called on the first modal dialog being shown. Adds the overlay and related 472 | * handlers. 473 | */ 474 | dialogPolyfill.DialogManager.prototype.blockDocument = function() { 475 | document.documentElement.addEventListener('focus', this.handleFocus_, true); 476 | document.addEventListener('keydown', this.handleKey_); 477 | this.mo_ && this.mo_.observe(document, {childList: true, subtree: true}); 478 | }; 479 | 480 | /** 481 | * Called on the first modal dialog being removed, i.e., when no more modal 482 | * dialogs are visible. 483 | */ 484 | dialogPolyfill.DialogManager.prototype.unblockDocument = function() { 485 | document.documentElement.removeEventListener('focus', this.handleFocus_, true); 486 | document.removeEventListener('keydown', this.handleKey_); 487 | this.mo_ && this.mo_.disconnect(); 488 | }; 489 | 490 | /** 491 | * Updates the stacking of all known dialogs. 492 | */ 493 | dialogPolyfill.DialogManager.prototype.updateStacking = function() { 494 | var zIndex = this.zIndexHigh_; 495 | 496 | for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) { 497 | dpi.updateZIndex(--zIndex, --zIndex); 498 | if (i === 0) { 499 | this.overlay.style.zIndex = --zIndex; 500 | } 501 | } 502 | 503 | // Make the overlay a sibling of the dialog itself. 504 | var last = this.pendingDialogStack[0]; 505 | if (last) { 506 | var p = last.dialog.parentNode || document.body; 507 | p.appendChild(this.overlay); 508 | } else if (this.overlay.parentNode) { 509 | this.overlay.parentNode.removeChild(this.overlay); 510 | } 511 | }; 512 | 513 | /** 514 | * @param {Element} candidate to check if contained or is the top-most modal dialog 515 | * @return {boolean} whether candidate is contained in top dialog 516 | */ 517 | dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function(candidate) { 518 | while (candidate = findNearestDialog(candidate)) { 519 | for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) { 520 | if (dpi.dialog === candidate) { 521 | return i === 0; // only valid if top-most 522 | } 523 | } 524 | candidate = candidate.parentElement; 525 | } 526 | return false; 527 | }; 528 | 529 | dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) { 530 | if (this.containedByTopDialog_(event.target)) { return; } 531 | 532 | event.preventDefault(); 533 | event.stopPropagation(); 534 | safeBlur(/** @type {Element} */ (event.target)); 535 | 536 | if (this.forwardTab_ === undefined) { return; } // move focus only from a tab key 537 | 538 | var dpi = this.pendingDialogStack[0]; 539 | var dialog = dpi.dialog; 540 | var position = dialog.compareDocumentPosition(event.target); 541 | if (position & Node.DOCUMENT_POSITION_PRECEDING) { 542 | if (this.forwardTab_) { // forward 543 | dpi.focus_(); 544 | } else { // backwards 545 | document.documentElement.focus(); 546 | } 547 | } else { 548 | // TODO: Focus after the dialog, is ignored. 549 | } 550 | 551 | return false; 552 | }; 553 | 554 | dialogPolyfill.DialogManager.prototype.handleKey_ = function(event) { 555 | this.forwardTab_ = undefined; 556 | if (event.keyCode === 27) { 557 | event.preventDefault(); 558 | event.stopPropagation(); 559 | var cancelEvent = new supportCustomEvent('cancel', { 560 | bubbles: false, 561 | cancelable: true 562 | }); 563 | var dpi = this.pendingDialogStack[0]; 564 | if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) { 565 | dpi.dialog.close(); 566 | } 567 | } else if (event.keyCode === 9) { 568 | this.forwardTab_ = !event.shiftKey; 569 | } 570 | }; 571 | 572 | /** 573 | * Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are 574 | * removed and immediately readded don't stay modal, they become normal. 575 | * 576 | * @param {!Array} removed that have definitely been removed 577 | */ 578 | dialogPolyfill.DialogManager.prototype.checkDOM_ = function(removed) { 579 | // This operates on a clone because it may cause it to change. Each change also calls 580 | // updateStacking, which only actually needs to happen once. But who removes many modal dialogs 581 | // at a time?! 582 | var clone = this.pendingDialogStack.slice(); 583 | clone.forEach(function(dpi) { 584 | if (removed.indexOf(dpi.dialog) !== -1) { 585 | dpi.downgradeModal(); 586 | } else { 587 | dpi.maybeHideModal(); 588 | } 589 | }); 590 | }; 591 | 592 | /** 593 | * @param {!dialogPolyfillInfo} dpi 594 | * @return {boolean} whether the dialog was allowed 595 | */ 596 | dialogPolyfill.DialogManager.prototype.pushDialog = function(dpi) { 597 | var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1; 598 | if (this.pendingDialogStack.length >= allowed) { 599 | return false; 600 | } 601 | if (this.pendingDialogStack.unshift(dpi) === 1) { 602 | this.blockDocument(); 603 | } 604 | this.updateStacking(); 605 | return true; 606 | }; 607 | 608 | /** 609 | * @param {!dialogPolyfillInfo} dpi 610 | */ 611 | dialogPolyfill.DialogManager.prototype.removeDialog = function(dpi) { 612 | var index = this.pendingDialogStack.indexOf(dpi); 613 | if (index === -1) { return; } 614 | 615 | this.pendingDialogStack.splice(index, 1); 616 | if (this.pendingDialogStack.length === 0) { 617 | this.unblockDocument(); 618 | } 619 | this.updateStacking(); 620 | }; 621 | 622 | dialogPolyfill.dm = new dialogPolyfill.DialogManager(); 623 | dialogPolyfill.formSubmitter = null; 624 | dialogPolyfill.useValue = null; 625 | 626 | /** 627 | * Installs global handlers, such as click listers and native method overrides. These are needed 628 | * even if a no dialog is registered, as they deal with
    . 629 | */ 630 | if (window.HTMLDialogElement === undefined) { 631 | 632 | /** 633 | * If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with 634 | * one that returns the correct value. 635 | */ 636 | var testForm = document.createElement('form'); 637 | testForm.setAttribute('method', 'dialog'); 638 | if (testForm.method !== 'dialog') { 639 | var methodDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, 'method'); 640 | if (methodDescriptor) { 641 | // nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything 642 | // and don't bother to update the element. 643 | var realGet = methodDescriptor.get; 644 | methodDescriptor.get = function() { 645 | if (isFormMethodDialog(this)) { 646 | return 'dialog'; 647 | } 648 | return realGet.call(this); 649 | }; 650 | var realSet = methodDescriptor.set; 651 | methodDescriptor.set = function(v) { 652 | if (typeof v === 'string' && v.toLowerCase() === 'dialog') { 653 | return this.setAttribute('method', v); 654 | } 655 | return realSet.call(this, v); 656 | }; 657 | Object.defineProperty(HTMLFormElement.prototype, 'method', methodDescriptor); 658 | } 659 | } 660 | 661 | /** 662 | * Global 'click' handler, to capture the or 108 | 109 |
    110 | 111 | 112 |
    113 | アカウントを削除しました。 114 |
    115 | 116 |
    117 | 118 |
    119 |
    120 | 121 | 122 |
    123 | 124 |
    125 | プロフィールが変更されました。 126 |
    127 | 128 |
    129 | 130 |
    131 |
    132 | 133 | 134 | 135 | 136 |
    137 |
    138 | 本当にアカウントを削除しますか? 139 | 処理を続行するにはメールアドレスを入力してください。 140 |
    141 | 142 |
    143 | 144 | 145 | 146 | 無効なメールアドレスです 147 |
    148 |
    149 | 150 |
    151 | 152 | 153 |
    154 |
    155 | 156 | 157 |
    158 |
    159 | 160 |
    161 | 162 |
    163 | 164 | リンク 165 | 166 |
    167 |
    168 | 169 |
    170 | 171 |
    172 | 173 | 174 |
    175 |
    176 | 177 |
    178 | 179 |
    180 | スレッド情報が編集されました。 181 |
    182 | 183 |
    184 | 185 |
    186 |
    187 | 188 | 189 |
    190 | スレッドが削除されました。 191 |
    192 | 193 |
    194 | 195 |
    196 |
    197 | 198 | 199 | 200 | 201 |
    202 | 本当にこのスレッドを削除してもよろしいですか? 203 |
    204 | 205 |
    206 | 207 | 208 |
    209 | 210 | 211 |
    212 | 213 | 214 | 215 | 216 |
    217 |
    218 | 219 | 220 |
    221 | 222 |
    223 | 224 | 225 |
    226 | 227 |
    228 | 229 | 230 |
    231 | 232 | 233 | 234 | 238 | 239 |
    240 | 241 | 242 |
    243 |
    244 | 245 |
    246 | 247 | 248 | 249 |
    250 | 251 | 252 |
    253 | 254 | 255 |
    256 |
    257 | このスレッドはパスワードで保護されています。 258 | 閲覧にはパスワードが必要です。 259 |
    260 | 261 |
    262 | 263 | 264 | 265 | パスワードが間違っています 266 |
    267 |
    268 | 269 |
    270 | 271 | 272 |
    273 | 274 | 275 | 276 |
    277 | 278 | 279 |
    280 | スレッド名 281 |
    スレッドの概要
    282 | 283 | 詳細... 284 |
    スレッドの詳細
    285 |
    286 | 287 |
    288 | 289 |
    290 | 291 | 292 |
    293 | 294 | 295 |
    296 | 297 | ユーザー 298 |
    299 | 300 | 303 | 304 |
      305 | 309 | 310 |
    • 311 | add_a_photo 312 | 画像埋め込み... 313 |
    • 314 | 315 |
    • 316 | attachment 317 | ファイル埋め込み... 318 |
    • 319 |
    320 |
    321 | 322 |
    323 |
    324 | 325 | 326 |
    327 |
    328 | 329 |
    330 | 331 | 332 |
    333 | 334 | 335 |
    336 | 337 | 338 |
    339 |
    340 | 341 | 342 | 343 | 無効なURL形式 344 |
    345 |
    346 | 347 |
    348 | 349 | 350 |
    351 |
    352 |
    353 | 354 | 355 | --------------------------------------------------------------------------------