├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── LICENSE.txt ├── README.md └── solve-later-again ├── dst ├── icon │ ├── icon_128x128.png │ ├── icon_16x16.png │ ├── icon_19x19.png │ ├── icon_32x32.png │ ├── icon_38x38.png │ └── icon_48x48.png └── manifest.json ├── img ├── promotion_1400x560.PNG ├── promotion_440x280.png ├── promotion_920x680.PNG ├── screenshot1_1280x800.png └── shopicon_128x128(96x96).PNG ├── package-lock.json ├── package.json ├── src ├── background.ts ├── click.ts ├── consts.ts ├── content_scripts.ts ├── dom_operations.ts ├── scss │ └── sla.scss ├── sla.ts └── utils.ts ├── tsconfig.json ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions 2 | name: Node CI 3 | 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | build: 8 | 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [10.x, 12.x] 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | 22 | - name: Tell me where workspace is 23 | working-directory: ${{ github.workspace }} 24 | run: ls -la 25 | 26 | - name: npm install, build, and test 27 | run: | 28 | npm ci 29 | npm run build --if-present 30 | # npm test 31 | working-directory: ./solve-later-again 32 | env: 33 | CI: true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore folowing files 2 | solve-later-again/node_modules 3 | solve-later-again/node_modules/** 4 | 5 | solve-later-again/dst/bin/ 6 | solve-later-again/dst/bin/** 7 | 8 | *.js.map 9 | 10 | # Don't ignore following files 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2019 taketakeyyy 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solve-later-again 2 | Chrome extension. 3 | 4 | You can manage problems you want to solve later again on [AtCoderProblems](https://kenkoooo.com/atcoder#/table/). 5 | 6 | ![Screenshot](/solve-later-again/img/screenshot1_1280x800.png "Screenshot") 7 | 8 | ## Getting Started 9 | 10 | [Install from Chrome Web Store](https://chrome.google.com/webstore/detail/solve-later-again/emndffmnlppiaelhdneheagpaancfahk). 11 | 12 | 13 | 1. When installed, the AtCoderProblems Table page will have a "Solve Later Again" table. 14 | 15 | 2. Checking a checkbox of a problem which you think "It would be better to try again🤔" will add the problem to the "Solve Later Again" table. 16 | 17 | 3. When you solved the problem, check the "Solved 1" check box. Then the date you solve is printed. 18 | 19 | 4. After 7 days, "Solved 2" will be highlighted, so try again to solve. 20 | 21 | 5. After 30 days, "Solved 3" will be highlighted, so try again to solve. 22 | 23 | 6. Omg, you would ... 24 | * never mistake similar problems😎. 25 | * become a strong strong man💪. 26 | 27 | 28 | # For Devs 29 | ## Project Files 30 | * `solve-later-again/dst/` 31 | * Files to publish for chrome extension. 32 | * `solve-later-again/src/` 33 | * They are built with webpack. 34 | 35 | ## Development Environment Installation 36 | ### node.js 37 | 10.16.0 LTS 38 | 39 | * Dowonload for Windows: https://nodejs.org/ja/ 40 | 41 | ### Install Development Environment 42 | 43 | Now, your current directory is the same to `solve-later-again/package.json`. Type following: 44 | 45 | `> npm ci` 46 | 47 | 48 | ### webpack 4 49 | * Installation: In the above "Install Development Environment" section, it should already be installed. If not, see [webpack - Installation](https://webpack.js.org/guides/installation/). 50 | 51 | * Version: See `solve-later-again/package.json` or `solve-later-again/package-lock.json`. 52 | 53 | 54 | ## How to develop 55 | Ther are all files to publish in `solve-later-again/dst/*`. 56 | 57 | 58 | ### content.js 59 | Now, your current directory is the same to `solve-later-again/node_modules` (This is generated when "Install Development Environment" section). Type following: 60 | 61 | `> npm run build-dev` 62 | 63 | This command means "build with watch-mode and development-mode". In detail, see `package.json` file. Then, fix and append codes in `solve-later-again/src/*.ts`. When built with webpack, `solve-later-again/dst/bin/*` is updated. 64 | 65 | Production build is: 66 | 67 | `> npm run build` 68 | 69 | 70 | ### Files that need to be edited directly 71 | 72 | The following files in `dst` are not automatically generated by webpack and must be edited directly. 73 | 74 | * `solve-later-again/dst/icon/*` 75 | * `solve-later-again/dst/manifest.json` 76 | 77 | 78 | ## Publish (and Update) 79 | 80 | The final files to publish are `solve-later-again/dst/*`. 81 | 82 | Zip `solve-later-again/dst/` and publish on the Chrome Web Store. 83 | -------------------------------------------------------------------------------- /solve-later-again/dst/icon/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/dst/icon/icon_128x128.png -------------------------------------------------------------------------------- /solve-later-again/dst/icon/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/dst/icon/icon_16x16.png -------------------------------------------------------------------------------- /solve-later-again/dst/icon/icon_19x19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/dst/icon/icon_19x19.png -------------------------------------------------------------------------------- /solve-later-again/dst/icon/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/dst/icon/icon_32x32.png -------------------------------------------------------------------------------- /solve-later-again/dst/icon/icon_38x38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/dst/icon/icon_38x38.png -------------------------------------------------------------------------------- /solve-later-again/dst/icon/icon_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/dst/icon/icon_48x48.png -------------------------------------------------------------------------------- /solve-later-again/dst/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Solve Later Again", 4 | "short_name": "SLA", 5 | "version": "1.4.0", 6 | "author": "taketakeyyy", 7 | "description": "Append a 'Solve Later Again' table on AtCoder Problems", 8 | 9 | "icons": { 10 | "16": "icon/icon_16x16.png", 11 | "32": "icon/icon_32x32.png", 12 | "48": "icon/icon_48x48.png", 13 | "128": "icon/icon_128x128.png" 14 | }, 15 | 16 | "action": { 17 | "default_title": "Solve Later Again", 18 | "default_icon": { 19 | "19": "icon/icon_19x19.png", 20 | "38": "icon/icon_38x38.png" 21 | } 22 | }, 23 | 24 | "content_scripts": [ 25 | { 26 | "matches": ["https://kenkoooo.com/atcoder/*"], 27 | "js": ["bin/content_scripts.js"] 28 | } 29 | ], 30 | 31 | "background": { 32 | "service_worker": "bin/background.js" 33 | }, 34 | 35 | "permissions": ["tabs", "storage"], 36 | 37 | "host_permissions": [ 38 | "https://kenkoooo.com/atcoder/*" 39 | ] 40 | } -------------------------------------------------------------------------------- /solve-later-again/img/promotion_1400x560.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/img/promotion_1400x560.PNG -------------------------------------------------------------------------------- /solve-later-again/img/promotion_440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/img/promotion_440x280.png -------------------------------------------------------------------------------- /solve-later-again/img/promotion_920x680.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/img/promotion_920x680.PNG -------------------------------------------------------------------------------- /solve-later-again/img/screenshot1_1280x800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/img/screenshot1_1280x800.png -------------------------------------------------------------------------------- /solve-later-again/img/shopicon_128x128(96x96).PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taketakeyyy/solve-later-again/fcf31040f176a86c2968915065f070caff10bd02/solve-later-again/img/shopicon_128x128(96x96).PNG -------------------------------------------------------------------------------- /solve-later-again/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solve-later-again", 3 | "version": "1.0.0", 4 | "description": "Chrome extention on AtCoder Problems", 5 | "main": "content.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tsc": "tsc", 9 | "build": "webpack --config webpack.prod.js", 10 | "build-dev": "webpack-cli -w --config webpack.dev.js" 11 | }, 12 | "keywords": [], 13 | "author": "taketakeyyy", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@babel/core": "^7.18.5", 17 | "@babel/preset-env": "^7.18.2", 18 | "@types/chrome": "^0.0.190", 19 | "babel-loader": "^8.2.5", 20 | "css-loader": "^6.7.1", 21 | "sass": "^1.52.3", 22 | "sass-loader": "^13.0.0", 23 | "style-loader": "^3.3.1", 24 | "ts-loader": "^9.3.0", 25 | "typescript": "^4.7.3", 26 | "webpack": "^5.73.0", 27 | "webpack-cli": "^4.10.0", 28 | "webpack-dev-server": "^4.9.2", 29 | "webpack-merge": "^5.8.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /solve-later-again/src/background.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // Content Scriptからメッセージを受け取った時の処理をchrome.runtime.onMessage.addListenerで書く 5 | // コールバック関数でメッセージを受け取った時の処理を書けばOK 6 | chrome.runtime.onMessage.addListener( 7 | function(message, sender, callback){ 8 | // 第一引数は受け取ったメッセージ 9 | // 第二引数はそれを送ってきたオブジェクト 10 | // 第三引数は受け取った後に実行したいコールバック関数を指定することができる 11 | 12 | return true; 13 | } 14 | ); 15 | 16 | 17 | // タブに変更があったとき(初めてページを開いたときも発火する) 18 | chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { 19 | // [TODO] Unchecked runtime.lastError: The message port closed before a response was received.はこのへんがあやしい 20 | 21 | if(changeInfo.status !== "complete"){ return false; } 22 | 23 | chrome.tabs.query( 24 | { 25 | active: true, 26 | lastFocusedWindow: true 27 | }, 28 | function(tabs) { 29 | // Tableページのみ処理する 30 | // https://kenkoooo.com/atcoder#/table/username 31 | // https://kenkoooo.com/atcoder/#/table/username 32 | // https://kenkoooo.com/atcoder/?user=username#/table/username/ 33 | const pattern = /^https?:\/\/kenkoooo\.com\/atcoder.*#\/table\/.*/g; 34 | const active_tab = tabs[0]; 35 | if (active_tab == null) { return false; } 36 | if (active_tab.url == null) { return false; } 37 | if (active_tab.id == null ) { return false; } 38 | 39 | const result = active_tab.url.match(pattern); 40 | if(result === null){ 41 | // Tableページでないならば 42 | return false; 43 | } 44 | 45 | // Tableページならば 46 | // アクティブタブにメッセージを送信する 47 | chrome.tabs.sendMessage(active_tab.id, {}, function(){}); 48 | return true; 49 | } 50 | ); 51 | 52 | return true; 53 | }); 54 | 55 | -------------------------------------------------------------------------------- /solve-later-again/src/click.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | make_new_tr_sla, 5 | unhilight_problem, 6 | initialize_problem_status, 7 | get_active_table_tab, 8 | make_checkboxes, 9 | } from "./dom_operations"; 10 | const consts = require("./consts"); 11 | 12 | 13 | /** 14 | * Againボタンをクリックしたときの処理 15 | * 16 | * @remarks SLAのテーブルのこの問題を初期状態に戻す 17 | * 18 | * @param e クリックされたボタンのイベント 19 | */ 20 | export function click_again_btn_sla(e: Event) { 21 | const again_btn: HTMLInputElement = e.target; 22 | const problem_name = again_btn.getAttribute("id")!.slice(consts.ID_AGAIN_BTN_SLA_.length); 23 | 24 | initialize_problem_status(problem_name); 25 | for (let i=1; ie.target; 45 | const problem_name = del_btn.getAttribute("id")!.slice(consts.ID_DEL_BTN_SLA_.length); 46 | 47 | // SLAのテーブルからこの問題を削除する 48 | const del_tr: HTMLTableRowElement = document.getElementById(consts.ID_TR_SLA_+problem_name); 49 | del_tr.parentNode!.removeChild(del_tr); 50 | 51 | // この問題のチェックボックスのチェックを外す 52 | const chkbox: HTMLInputElement = document.getElementById(consts.ID_CHKBOX_SLA_+problem_name); 53 | if (chkbox !== null) { 54 | chkbox.checked = false; 55 | } 56 | 57 | // 現在のテーブル状態を保存する 58 | save_solve_later_again(problem_name); 59 | } 60 | 61 | /** 62 | * 問題のチェックボックスがクリックされたときの処理 63 | * 64 | * @param e クリックされたチェックボックスのイベント 65 | */ 66 | export function click_chkbox_sla(e: Event) { 67 | const chkbox: HTMLInputElement = e.target; 68 | const problem_name = chkbox.getAttribute("id")!.slice(consts.ID_CHKBOX_SLA_.length); 69 | 70 | if(chkbox.checked) { 71 | // SLAテーブルにこの問題を追加する 72 | const target_td: HTMLTableCellElement = chkbox.parentNode; 73 | const a_tag: HTMLAnchorElement = target_td.getElementsByTagName("a")[0].cloneNode(true); 74 | make_new_tr_sla(problem_name, a_tag); 75 | } 76 | else{ 77 | // SLAテーブルからこの問題を削除する 78 | const elem_del: HTMLTableRowElement = document.getElementById(consts.ID_TR_SLA_+problem_name); 79 | elem_del.parentNode!.removeChild(elem_del); 80 | } 81 | 82 | // 現在のテーブル状態を保存する 83 | save_solve_later_again(problem_name); 84 | } 85 | 86 | /** 87 | * SLAテーブルの状態を保存する 88 | * 89 | * @remarks 90 | * * 保存形式は、以下のような感じ。 91 | * - チェックボックスのとき、null 92 | * - 日付が入ってる場合、それをstr型で保存 93 | * 94 | * * 例 95 | * - {sla_abc131_a: {"solved1": "2019/4/21(Sun)"}, {"solved2": null}, {"solved3": null}} 96 | * 97 | * {@link https://dackdive.hateblo.jp/entry/2017/07/27/100000} 98 | * {@link https://github.com/taketakeyyy/my-practice/blob/master/dotinstall/MyExtensions/04_OptionsUI/options.js} 99 | * 100 | * @param problem_name コンテスト名 (abc131_a) 101 | */ 102 | function save_solve_later_again(problem_name: string) { 103 | const sla_id = "sla_" + problem_name; 104 | 105 | if(document.getElementById(consts.ID_TR_SLA_+problem_name) === null) { 106 | // SLAテーブルに要素が存在しないなら、データを削除する 107 | chrome.storage.sync.remove(sla_id); 108 | return true; 109 | } 110 | 111 | // SLAテーブルに要素が存在するなら、現在の状態を保存する 112 | let saving_data: any = {}; 113 | saving_data[sla_id] = {}; 114 | 115 | // problem_nameやurlやdifficultyなどの属性を保存する 116 | const target_tr: HTMLTableRowElement = document.getElementById("tr_"+sla_id); 117 | const a_tag: HTMLAnchorElement = target_tr.getElementsByTagName("a")[0]; 118 | saving_data[sla_id][consts.STORAGE_KEY_PROBLEM_NAME] = a_tag.innerText; 119 | saving_data[sla_id][consts.STORAGE_KEY_URL] = a_tag.href; 120 | saving_data[sla_id][consts.STORAGE_KEY_DIFFICULTY] = a_tag.className; 121 | 122 | // solvedチェックボックスの状態を保存する 123 | for(let i=1; i<=consts.SOLVED_MAX; i++) { 124 | const chkbox = document.getElementById("chkbox_solved"+String(i)+"_"+sla_id); 125 | if(chkbox === null){ 126 | const div: HTMLDivElement = document.getElementById("date_solved"+String(i)+"_"+sla_id); 127 | saving_data[sla_id]["solved"+String(i)] = div.innerText; 128 | continue; 129 | } 130 | saving_data[sla_id]["solved"+String(i)] = null; 131 | } 132 | 133 | chrome.storage.sync.set(saving_data); 134 | } 135 | 136 | 137 | /** 138 | * Solved Later Againテーブルの Solved のチェックボックスをクリックしたときの処理 139 | * 140 | * @remarks 141 | * 142 | * 1. Solved1のチェックが入ったら、クリックされた年月日をいれて、Solved2をクリック可能にする 143 | * 2. Solved2のチェックが入ったら、クリックされた年月日をいれて、Solved3をクリック可能にする 144 | * 3. Solved3のチェックが入ったら、クリックされた年月日をいれる 145 | * 146 | * @param e クリックされたチェックボックスのイベント 147 | */ 148 | export function click_chkbox_solved_sla(e: Event) { 149 | 150 | // このチェックボックスのidは、"chkbox_solved*_sla_project_problem" 151 | const chkbox: HTMLInputElement = e.target; 152 | const problem_name = chkbox.getAttribute("id")!.slice(consts.ID_CHKBOX_SOLVED1_SLA_.length); 153 | const solved_num = parseInt(chkbox.getAttribute("id")!["chkbox_solved".length], 10); 154 | 155 | const func = (solved_num: number) => { 156 | const now = new Date(); 157 | const year = now.getFullYear(); 158 | const month = now.getMonth(); 159 | const day = now.getDate(); 160 | const wday = now.getDay(); 161 | const td: HTMLTableCellElement = chkbox.parentNode; 162 | td.innerText = ""; 163 | const div: HTMLDivElement = document.createElement("div"); 164 | div.setAttribute("id", "date_solved"+String(solved_num)+"_sla_"+problem_name); 165 | div.innerText = year + "/" + (month+1) + "/" + day + "(" + consts.WDAYS[wday] + ")"; 166 | td.appendChild(div); 167 | 168 | if(solved_num < consts.SOLVED_MAX) { 169 | const chkbox_solved_next: HTMLInputElement = document.getElementById("chkbox_solved"+(solved_num+1)+"_sla_"+problem_name); 170 | chkbox_solved_next.disabled = false; 171 | } 172 | } 173 | 174 | func(solved_num); 175 | 176 | unhilight_problem(problem_name, solved_num); 177 | 178 | // 現在のテーブル状態を保存する 179 | save_solve_later_again(problem_name); 180 | } 181 | 182 | 183 | /** 184 | * Show SLA Table のチェックボックスをクリックしたときのクリックイベント処理 185 | * 186 | * @param e クリックされたチェックボックスのイベント 187 | */ 188 | export function click_show_sla_table_sla(e: Event) { 189 | const chkbox: HTMLInputElement = e.target; 190 | on_click_show_sla_table_sla(chkbox.checked, true); 191 | } 192 | 193 | 194 | /** 195 | * Show SLA Table のチェックボックスをクリックしたときの実際の処理 196 | * 197 | * @param is_checked チェックボックスがチェック状態かどうか 198 | * @param do_save 状態を保存するかどうか 199 | * 200 | */ 201 | export function on_click_show_sla_table_sla(is_checked: boolean, do_save: boolean) { 202 | const div_sla_table_container: HTMLDivElement = document.getElementById(consts.DIV_SLA_TABLE_CONTAINER); 203 | 204 | if (is_checked) { 205 | div_sla_table_container.setAttribute("style", "display: block"); 206 | } 207 | else{ 208 | div_sla_table_container.setAttribute("style", "display: none"); 209 | } 210 | 211 | if (do_save) { 212 | _save_show_sla_table(is_checked); 213 | } 214 | 215 | } 216 | 217 | 218 | /** 219 | * Show SLA Tableの状態を保存する 220 | * 221 | * @param is_checked チェックあり(true)/チェックなし(false) 222 | */ 223 | function _save_show_sla_table(is_checked: boolean) { 224 | let save_data: any = {}; 225 | save_data["show_sla_table"] = is_checked; 226 | 227 | chrome.storage.sync.set(save_data); 228 | } 229 | 230 | 231 | /** 232 | * ストレージを読み込んで、各問題のチェックボックスにチェックを入れる 233 | */ 234 | export const check_checkboxes = async (): Promise => { 235 | chrome.storage.sync.get(null, function(loaded_data) { 236 | // console.log(loaded_data) 237 | for(let sla_id in loaded_data) { 238 | const target_chkbox: HTMLInputElement = document.getElementById("chkbox_"+sla_id); 239 | if (target_chkbox === null) continue; 240 | 241 | if (sla_id === "show_sla_table") { 242 | target_chkbox.checked = loaded_data[sla_id]; 243 | continue; 244 | } 245 | 246 | target_chkbox.checked = true; // 問題のチェックボックスにチェックを入れる 247 | } 248 | return; 249 | }); 250 | } 251 | 252 | 253 | /** 254 | * チェックボックスを作り直す 255 | */ 256 | export const click_remake_chkboxes = async (e: Event) => { 257 | const pre_active_tab = get_active_table_tab(); 258 | if (e.target === pre_active_tab) { 259 | // 移動先のタブと、現在のアクティブタブが同じなら何もしない 260 | return; 261 | } 262 | 263 | // 各問題にチェックボックスをつける 264 | await make_checkboxes(); 265 | await check_checkboxes(); 266 | } -------------------------------------------------------------------------------- /solve-later-again/src/consts.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const SOLVED_MAX = 3; // Solved3 まで 4 | 5 | export const ABC_COL_NUM = 7; // ABCの列数 6 | export const ARC_COL_NUM = 7; // ARCの列数 7 | export const AGC_COL_NUM = 8; // AGCの列数 8 | export const ABCLIKE_COL_NUM = 7; // ABC-Likeの列数 9 | export const ARCLIKE_COL_NUM = 7; // ARC-Likeの列数 10 | 11 | export const ID_SLA_ROOT = "sla_root"; // Solve Later Again(SLA)テーブルのdivのid 12 | export const ID_TR_SLA_ = "tr_sla_"; // SLAテーブルのtr要素のidのprefix (tr_sla_abc131_a) 13 | export const ID_CHKBOX_SOLVED1_SLA_ = "chkbox_solved1_sla_"; // SLAテーブルのSolved1列のチェックボックスのIDのprefix (chkbox_solved1_sla_abc131_a) 14 | export const ID_CHKBOX_SOLVED2_SLA_ = "chkbox_solved2_sla_"; // SLAテーブルのSolved2列のチェックボックスのIDのprefix (chkbox_solved2_sla_abc131_a) 15 | export const ID_CHKBOX_SOLVED3_SLA_ = "chkbox_solved3_sla_"; // SLAテーブルのSolved3列のチェックボックスのIDのprefix (chkbox_solved3_sla_abc131_a) 16 | export const ID_DEL_BTN_SLA_ = "del_btn_sla_"; // SLAテーブルのDeleteボタンのIDのprefix (del_btn_sla_abc131_a) 17 | export const ID_AGAIN_BTN_SLA_ = "again_btn_sla_"; // SLAテーブルのAgainボタンのIDのprefix (again_btn_sla_abc131_a) 18 | export const ID_DATE_SOLVED1_SLA_ = "date_solved1_sla_"; // SLAテーブルの問題を解いたときの年月日のdivのidのprefix (date_solved1_sla_abc131_a) 19 | export const ID_DATE_SOLVED2_SLA_ = "date_solved2_sla_"; // SLAテーブルの問題を解いたときの年月日のdivのidのprefix (date_solved2_sla_abc131_a) 20 | export const ID_DATE_SOLVED3_SLA_ = "date_solved3_sla_"; // SLAテーブルの問題を解いたときの年月日のdivのidのprefix (date_solved3_sla_abc131_a) 21 | export const ID_CHKBOX_SHOW_SLA_TABLE = "chkbox_show_sla_table"; // Show SLA Tableのチェックボックスのid 22 | export const DIV_SLA_TABLE_CONTAINER = "div_sla_table_container"; // Show SLA Tableのチェックボックスで表示/非表示するdiv要素のid 23 | 24 | export const ID_CHKBOX_SLA_ = "chkbox_sla_"; // 各問題のチェックボックスのIDのprefix (chkbox_sla_abc131_a) 25 | 26 | export const CLASS_CHKBOX_SLA = "chkbox_sla"; // 各問題のチェックボックスのclass 27 | 28 | export const SOLVED2_DAYS = 7; // Solved2 はX日後に解き直す 29 | export const SOLVED3_DAYS = 30; // Solved3 はX日後に解き直す 30 | 31 | export const WDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 32 | 33 | export const HILIGHT_CLR_TR = "#f5b88791"; 34 | export const HILIGHT_CLR_TD = "#f7964891"; 35 | 36 | export const CAN_MAKE_CHKBOX_WAIT_MSEC = 400; // ページのDOMが構成されるのを待つミリ秒 37 | export const CAN_MAKE_CHKBOX_RETRY_COUNT = 20; // ページのDOMが構成されるのを待つリトライ回数 38 | 39 | // storage_data[sla_id][STORAGE_KEY_*] 40 | export const STORAGE_KEY_PROBLEM_NAME = "problem_name"; 41 | export const STORAGE_KEY_URL = "url"; 42 | export const STORAGE_KEY_DIFFICULTY = "difficulty"; -------------------------------------------------------------------------------- /solve-later-again/src/content_scripts.ts: -------------------------------------------------------------------------------- 1 | (function(){ 2 | // スコープの汚染を防ぐため、即時関数で囲む 3 | 'use strict'; 4 | const sla = require("./sla"); 5 | 6 | // [START Entry Point] 7 | chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse){ 8 | sla.run_solve_later_again(); 9 | }); 10 | // [END Entry Point] 11 | 12 | })(); 13 | 14 | -------------------------------------------------------------------------------- /solve-later-again/src/dom_operations.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | click_chkbox_sla, 5 | click_chkbox_solved_sla, 6 | click_del_btn_sla, 7 | click_again_btn_sla, 8 | click_show_sla_table_sla, 9 | on_click_show_sla_table_sla, 10 | click_remake_chkboxes, 11 | } from "./click"; 12 | import { do_sleep, strdate2date } from "./utils"; 13 | const consts = require("./consts"); 14 | 15 | /** 16 | * SLA テーブルを作成する 17 | * 18 | * @remarks 19 | * 20 | * SLA テーブルとは、 「Solve Later Again」を h2 要素に持つブロックのこと。 21 | */ 22 | export const make_sla_table = async (): Promise => { 23 | chrome.storage.sync.get(null, function(loaded_data: any) { 24 | for (let sla_id in loaded_data) { 25 | if (sla_id === "show_sla_table") { 26 | const chkbox_show_sla_table: HTMLInputElement = document.getElementById(consts.ID_CHKBOX_SHOW_SLA_TABLE); 27 | chkbox_show_sla_table.checked = loaded_data["show_sla_table"]; 28 | on_click_show_sla_table_sla(loaded_data["show_sla_table"], false); 29 | continue; 30 | } 31 | 32 | // SLAテーブルにtr要素を作成する 33 | // const target_chkbox = document.getElementById("chkbox_"+sla_id); 34 | 35 | // TODO ABCタグを開いている状態で、Other Contestsの保存したチェックボックスを参照しようとしてエラーが出ている 36 | // const a_tag = target_chkbox.parentNode.getElementsByTagName("a")[0].cloneNode(true); 37 | let a_tag: HTMLAnchorElement|null = null; 38 | if (loaded_data[sla_id]["problem_name"] !== undefined) { 39 | a_tag = document.createElement("a"); 40 | a_tag.setAttribute("target", "_blank"); 41 | a_tag.setAttribute("rel", "noopener"); 42 | a_tag.innerText = loaded_data[sla_id][consts.STORAGE_KEY_PROBLEM_NAME]; 43 | a_tag.setAttribute("href", loaded_data[sla_id][consts.STORAGE_KEY_URL]); 44 | a_tag.setAttribute("class", loaded_data[sla_id][consts.STORAGE_KEY_DIFFICULTY]); 45 | } 46 | else { 47 | // ABCタグを開いている状態で、Other Contestsの保存したチェックボックスを参照しようとするとエラーが出るが、後方互換性を保つためにできるだけ実行する 48 | const target_chkbox: HTMLInputElement = document.getElementById("chkbox_"+sla_id); 49 | try { 50 | const td: HTMLTableCellElement = target_chkbox.parentNode; 51 | a_tag = td.getElementsByTagName("a")[0].cloneNode(true); 52 | } 53 | catch (e) { 54 | continue; 55 | } 56 | } 57 | if (a_tag != null) { 58 | make_new_tr_sla(sla_id.slice(4), a_tag); 59 | } 60 | 61 | // tr要素のSolved列が年月日の場合はそれに変更する 62 | for (let i=1; i<=consts.SOLVED_MAX; i++) { 63 | const solved_date = loaded_data[sla_id]["solved"+String(i)]; 64 | if(solved_date === null){ continue; } 65 | const parent_td: HTMLTableCellElement = document.getElementById("chkbox_solved"+String(i)+"_"+sla_id)!.parentNode; 66 | parent_td.innerText = ""; 67 | const div = document.createElement("div"); 68 | div.setAttribute("id", "date_solved"+String(i)+"_"+sla_id); 69 | div.innerText = solved_date; 70 | parent_td.appendChild(div); 71 | } 72 | 73 | // tr要素のSolved列のチェックボックスの中で、クリック可能にするものを決定する 74 | for (let i=1; i<=consts.SOLVED_MAX; i++) { 75 | const chkbox: HTMLInputElement = document.getElementById("chkbox_solved"+String(i)+"_"+sla_id); 76 | if(chkbox === null){ continue; } 77 | chkbox.disabled = false; 78 | break; 79 | } 80 | } 81 | return; 82 | }); 83 | } 84 | 85 | 86 | /** 87 | * 問題のチェックボックスを作成する 88 | * 89 | * @param problem_name id 作成に必要な問題名 90 | */ 91 | const new_checkbox_element = (problem_name: string): HTMLInputElement => { 92 | const new_chkbox = document.createElement("input"); 93 | new_chkbox.setAttribute("type", "checkbox"); 94 | new_chkbox.setAttribute("id", consts.ID_CHKBOX_SLA_ + problem_name); 95 | new_chkbox.setAttribute("class", consts.CLASS_CHKBOX_SLA); 96 | new_chkbox.checked = false; 97 | new_chkbox.addEventListener("click", click_chkbox_sla); 98 | return new_chkbox; 99 | } 100 | 101 | 102 | /** 103 | * 各問題の td 要素にチェックボックスを作成する。 104 | * 105 | * @remarks 106 | * 107 | * class="react-bs-table-container" があり、class="sla-table-container"を持たない div 要素を取得する。 108 | * その中の class に "table-problem" を持ち、 "table-problem-empty" を持たない td 要素にチェックボックスを追加する。 109 | * チェックボックスの id は、id="chkbox_sla_abc255_a" のようになる。 110 | * 111 | * @returns 追加したチェックボックスの数 112 | * 113 | */ 114 | const _append_checkboxes = async (): Promise => { 115 | // ターゲットとなる td 要素を探す 116 | const tds = document.getElementsByClassName("table-problem"); 117 | 118 | let append_num = 0; 119 | for(let i=0; itds[i]; 122 | if (target_td.classList.contains("table-problem-empty")) { continue; } 123 | 124 | // すでにチェックボックスが存在していればスキップ 125 | const inputs: HTMLCollectionOf = >target_td.getElementsByTagName("input"); 126 | let is_exist_chkbox = false; 127 | for(let i=0; itarget_td.getElementsByTagName("a")[0]; 137 | const href_texts: string[] = a_tag.href.split("/"); 138 | const problem_name: string = href_texts[href_texts.length-1]; 139 | 140 | // チェックボックス作成・追加 141 | const new_chkbox = new_checkbox_element(problem_name); 142 | target_td.insertBefore(new_chkbox, target_td.firstChild); 143 | append_num++; 144 | } 145 | 146 | return append_num; 147 | } 148 | 149 | 150 | /** 151 | * 各問題にチェックボックスを作る。 152 | * 153 | * @remarks 154 | * 155 | * 各問題にチェックボックスを作成するとき、以下のステップで進める。 156 | * 157 | * 1. 1個も作成できなかったとき、少し時間を置いてリトライする。1個以上作成できたら 2 へ。 158 | * 2. 1個以上作成できたとき、少し時間を置いてリトライする。1個も作成できなくなったら、終了。 159 | */ 160 | export const make_checkboxes = async () => { 161 | let first_success = false; // 1個以上作成できたかどうか 162 | 163 | for(let i=0; i 0) { first_success = true; } 174 | await do_sleep(consts.CAN_MAKE_CHKBOX_WAIT_MSEC); 175 | continue; 176 | } 177 | else { 178 | if (append_num > 0) { 179 | // まだ作成できる余地があるので、再度実行 180 | await do_sleep(consts.CAN_MAKE_CHKBOX_WAIT_MSEC); 181 | } 182 | else { 183 | // すべて作成し終わったみなし、終了 184 | break; 185 | } 186 | } 187 | } 188 | } 189 | 190 | /** 191 | * Solve Later Again セクションの基本的なHTMLを作成する 192 | */ 193 | export function make_base_html(){ 194 | const html = document.createElement("div"); 195 | html.setAttribute("id", consts.ID_SLA_ROOT); 196 | html.classList.add("row"); 197 | 198 | const h2 = document.createElement("h2"); 199 | h2.textContent = "Solve Later Again"; 200 | h2.setAttribute("class", "sla-h2"); 201 | html.appendChild(h2); 202 | 203 | html.appendChild(_make_show_sla_table_chkbox()); 204 | 205 | const div_react_bs_table_container = document.createElement("div"); 206 | div_react_bs_table_container.classList.add("react-bs-table-container"); 207 | div_react_bs_table_container.classList.add("sla-table-container"); 208 | div_react_bs_table_container.setAttribute("id", consts.DIV_SLA_TABLE_CONTAINER); 209 | html.appendChild(div_react_bs_table_container); 210 | 211 | const div_react_bs_table = document.createElement("div"); 212 | div_react_bs_table.classList.add("react-bs-table"); 213 | div_react_bs_table.classList.add("react-bs-table-bordered"); 214 | div_react_bs_table_container.appendChild(div_react_bs_table); 215 | 216 | const div_s_alert_wrapper = document.createElement("div"); 217 | div_s_alert_wrapper.classList.add("s-alert-wrapper"); 218 | div_react_bs_table_container.appendChild(div_s_alert_wrapper); 219 | 220 | const div_react_bs_container_header = document.createElement("div"); 221 | div_react_bs_container_header.classList.add("react-bs-container-header"); 222 | div_react_bs_container_header.classList.add("table-header-wrapper"); 223 | div_react_bs_table.appendChild(div_react_bs_container_header); 224 | 225 | const div_react_bs_container_body = document.createElement("div"); 226 | div_react_bs_container_body.classList.add("react-bs-container-body"); 227 | div_react_bs_table.appendChild(div_react_bs_container_body); 228 | 229 | const table_container_header = document.createElement("table"); 230 | table_container_header.classList.add("table"); 231 | table_container_header.classList.add("table-hover"); 232 | table_container_header.classList.add("table-bordered"); 233 | div_react_bs_container_header.appendChild(table_container_header); 234 | 235 | const table_container_body = document.createElement("table"); 236 | table_container_body.classList.add("table"); 237 | table_container_body.classList.add("table-bordered"); 238 | div_react_bs_container_body.appendChild(table_container_body); 239 | 240 | const colgroup_container_header = document.createElement("colgroup"); 241 | table_container_header.appendChild(colgroup_container_header); 242 | 243 | const colgroup_container_body = document.createElement("colgroup"); 244 | table_container_body.appendChild(colgroup_container_body); 245 | 246 | const col_header1 = document.createElement("col"); 247 | const col_header2 = document.createElement("col"); 248 | const col_header3 = document.createElement("col"); 249 | const col_header4 = document.createElement("col"); 250 | const col_header5 = document.createElement("col"); 251 | colgroup_container_header.appendChild(col_header1); 252 | colgroup_container_header.appendChild(col_header2); 253 | colgroup_container_header.appendChild(col_header3); 254 | colgroup_container_header.appendChild(col_header4); 255 | colgroup_container_header.appendChild(col_header5); 256 | 257 | const col_body1 = document.createElement("col"); 258 | const col_body2 = document.createElement("col"); 259 | const col_body3 = document.createElement("col"); 260 | const col_body4 = document.createElement("col"); 261 | const col_body5 = document.createElement("col"); 262 | colgroup_container_body.appendChild(col_body1); 263 | colgroup_container_body.appendChild(col_body2); 264 | colgroup_container_body.appendChild(col_body3); 265 | colgroup_container_body.appendChild(col_body4); 266 | colgroup_container_body.appendChild(col_body5); 267 | 268 | const thead = document.createElement("thead"); 269 | table_container_header.appendChild(thead); 270 | 271 | const tr_thead = document.createElement("tr"); 272 | thead.appendChild(tr_thead); 273 | 274 | const th_problem = document.createElement("th"); 275 | th_problem.textContent = "Problem"; 276 | tr_thead.appendChild(th_problem); 277 | 278 | const th_solved1 = document.createElement("th"); 279 | th_solved1.textContent = "Solved 1"; 280 | tr_thead.appendChild(th_solved1); 281 | 282 | const th_solved2 = document.createElement("th"); 283 | th_solved2.textContent = "Solved 2 ("+String(consts.SOLVED2_DAYS)+" Days Later)"; 284 | tr_thead.appendChild(th_solved2); 285 | 286 | const th_solved3 = document.createElement("th"); 287 | th_solved3.textContent = "Solved 3 ("+String(consts.SOLVED3_DAYS)+" Days Later)"; 288 | tr_thead.appendChild(th_solved3); 289 | 290 | const th_buttons = document.createElement("th"); 291 | th_buttons.textContent = "Buttons"; 292 | th_buttons.colSpan = 2; 293 | tr_thead.appendChild(th_buttons); 294 | 295 | const tbody = document.createElement("tbody"); 296 | table_container_body.appendChild(tbody); 297 | 298 | return html; 299 | } 300 | 301 | 302 | /** SLAテーブルの新しいtr要素を作成する */ 303 | export function make_new_tr_sla(problem_name: string, a_tag: HTMLAnchorElement){ 304 | const tr = document.createElement("tr"); 305 | tr.setAttribute("id", consts.ID_TR_SLA_+problem_name); 306 | 307 | const td1 = document.createElement("td"); 308 | td1.appendChild(a_tag); 309 | 310 | // Solved1, 2, 3の td要素。あとで初期化する 311 | const td2 = document.createElement("td"); 312 | const td3 = document.createElement("td"); 313 | const td4 = document.createElement("td"); 314 | 315 | const td5 = document.createElement("td"); 316 | td5.classList.add("td-sla-again"); 317 | const btn_again = document.createElement("input"); 318 | btn_again.setAttribute("type", "button"); 319 | btn_again.setAttribute("value", "ReAgain"); 320 | btn_again.setAttribute("id", consts.ID_AGAIN_BTN_SLA_+problem_name); 321 | btn_again.classList.add("btn"); 322 | //btn_again.classList.add("btn-secondary"); 323 | btn_again.classList.add("btn-sla-again"); 324 | btn_again.addEventListener("click", click_again_btn_sla); 325 | td5.appendChild(btn_again); 326 | 327 | const td6 = document.createElement("td"); 328 | td6.classList.add("td-sla-delete"); 329 | const btn_del = document.createElement("input"); 330 | btn_del.setAttribute("type", "button"); 331 | btn_del.setAttribute("value", "Delete"); 332 | btn_del.setAttribute("id", consts.ID_DEL_BTN_SLA_+problem_name); 333 | btn_del.classList.add("btn"); 334 | btn_del.classList.add("btn-secondary"); 335 | btn_del.classList.add("btn-sla-delete"); 336 | btn_del.addEventListener("click", click_del_btn_sla); 337 | td6.appendChild(btn_del); 338 | 339 | tr.appendChild(td1); 340 | tr.appendChild(td2); 341 | tr.appendChild(td3); 342 | tr.appendChild(td4); 343 | tr.appendChild(td5); 344 | tr.appendChild(td6); 345 | 346 | // Solve Later Againのtbodyに、tr要素を追加する 347 | const root_div: HTMLDivElement = document.getElementById(consts.ID_SLA_ROOT); 348 | const tbody: HTMLTableSectionElement = root_div!.getElementsByTagName("tbody")[0]; 349 | tbody.appendChild(tr); 350 | 351 | initialize_problem_status(problem_name); 352 | } 353 | 354 | 355 | /** SLAテーブルの指定の問題の状態を初期化する */ 356 | export function initialize_problem_status(problem_name: string){ 357 | const target_tr: HTMLTableRowElement = document.getElementById(consts.ID_TR_SLA_+problem_name); 358 | const tds = target_tr.getElementsByTagName("td"); 359 | 360 | // solved1 361 | tds[1].textContent = null; // 子要素を全て削除 362 | const chkbox1 = document.createElement("input"); 363 | chkbox1.setAttribute("type", "checkbox"); 364 | chkbox1.setAttribute("id", consts.ID_CHKBOX_SOLVED1_SLA_+problem_name); 365 | chkbox1.addEventListener("click", click_chkbox_solved_sla); 366 | tds[1].appendChild(chkbox1); 367 | chkbox1.checked = false; 368 | chkbox1.disabled = false; 369 | 370 | // solved2 371 | tds[2].textContent = null; 372 | const chkbox2 = document.createElement("input"); 373 | chkbox2.setAttribute("type", "checkbox"); 374 | chkbox2.setAttribute("id", consts.ID_CHKBOX_SOLVED2_SLA_+problem_name); 375 | chkbox2.addEventListener("click", click_chkbox_solved_sla); 376 | tds[2].appendChild(chkbox2); 377 | chkbox2.checked = false; 378 | chkbox2.disabled = true; 379 | 380 | // solved3 381 | tds[3].textContent = null; 382 | const chkbox3 = document.createElement("input"); 383 | chkbox3.setAttribute("type", "checkbox"); 384 | chkbox3.setAttribute("id", consts.ID_CHKBOX_SOLVED3_SLA_+problem_name); 385 | chkbox3.addEventListener("click", click_chkbox_solved_sla); 386 | tds[3].appendChild(chkbox3); 387 | chkbox3.checked = false; 388 | chkbox3.disabled = true; 389 | } 390 | 391 | 392 | /** 規定の日数を経過したSLAテーブルの問題をハイライトする */ 393 | export const hilight_problems = async () => { 394 | const today = new Date(); 395 | today.setHours(23); 396 | today.setMinutes(59); 397 | today.setSeconds(59); 398 | 399 | // SLAテーブルの各問題を走査する 400 | const root_div: HTMLDivElement = document.getElementById(consts.ID_SLA_ROOT); 401 | const tbody = root_div.getElementsByTagName("tbody")[0]; 402 | const trs = tbody.getElementsByTagName("tr"); 403 | for(let i=0; idocument.getElementById(consts.ID_CHKBOX_SOLVED1_SLA_+problem_name); 408 | if(chkbox !== null && chkbox.disabled === false){ 409 | // Soved1をまだチェックしていないので、何もしない 410 | continue; 411 | } 412 | 413 | const hilight_if_needed = (solved_num: number, need_msec: number) => { 414 | /* SLAテーブルの問題を、日数が経過していればハイライトする */ 415 | const chkbox: HTMLInputElement = document.getElementById("chkbox_solved"+String(solved_num)+"_sla_"+problem_name); 416 | if(chkbox !== null && chkbox.disabled === false){ 417 | // このSovedをまだチェックしていないので、前のSolvedの日付と比較する 418 | const dt_str = document.getElementById("date_solved"+String(solved_num-1)+"_sla_"+problem_name)!.innerText; 419 | const dt = strdate2date(dt_str); 420 | // const dt = strdate2date("2019/6/1(Hoge)"); // テスト用 421 | const elapsed_msec: number = today.getTime() - dt.getTime(); 422 | if(elapsed_msec >= need_msec){ 423 | // 経過しているのでハイライト 424 | const target_tr: HTMLTableRowElement = document.getElementById(consts.ID_TR_SLA_+problem_name); 425 | target_tr.style.backgroundColor = consts.HILIGHT_CLR_TR; 426 | tds[solved_num].style.backgroundColor = consts.HILIGHT_CLR_TD; 427 | return true; 428 | } 429 | return false; 430 | } 431 | } 432 | 433 | // Solved 2をチェックする 434 | let result = hilight_if_needed(2, 60*60*24*consts.SOLVED2_DAYS*1000); 435 | if(result){ continue; } 436 | 437 | // Solved 3をチェックする 438 | result = hilight_if_needed(3, 60*60*24*consts.SOLVED3_DAYS*1000); 439 | if(result){ continue; } 440 | } 441 | } 442 | 443 | 444 | /** 445 | * SLAテーブルの指定の問題のハイライトを解除する 446 | * 447 | * @param problem_name 問題名 448 | * 449 | * @param solved_num solved 番号 450 | */ 451 | export function unhilight_problem(problem_name: string, solved_num: number){ 452 | const target_tr: HTMLTableRowElement = document.getElementById(consts.ID_TR_SLA_+problem_name); 453 | target_tr.style.backgroundColor = ""; 454 | 455 | const tds = target_tr.getElementsByTagName("td"); 456 | tds[solved_num].style.backgroundColor = ""; 457 | } 458 | 459 | 460 | /** Show SLA Table のチェックボックスを作成する */ 461 | function _make_show_sla_table_chkbox(){ 462 | 463 | const show_sla_table_chkbox = document.createElement("input"); 464 | show_sla_table_chkbox.setAttribute("type", "checkbox"); 465 | show_sla_table_chkbox.setAttribute("class", "form-check-input"); 466 | show_sla_table_chkbox.setAttribute("id", consts.ID_CHKBOX_SHOW_SLA_TABLE); 467 | show_sla_table_chkbox.addEventListener("click", click_show_sla_table_sla); 468 | 469 | const show_sla_table_label = document.createElement("label"); 470 | show_sla_table_label.setAttribute("class", "form-check-label"); 471 | show_sla_table_label.innerText = "Show SLA Table"; 472 | show_sla_table_label.insertBefore(show_sla_table_chkbox, show_sla_table_label.firstChild); 473 | 474 | const show_sla_table_div = document.createElement("div"); 475 | show_sla_table_div.setAttribute("class", "form-check form-check-inline"); 476 | show_sla_table_div.insertBefore(show_sla_table_label, show_sla_table_div.firstChild); 477 | 478 | const base_div = document.createElement("div"); 479 | base_div.setAttribute("class", ""); 480 | base_div.insertBefore(show_sla_table_div, base_div.firstChild); 481 | 482 | return base_div; 483 | } 484 | 485 | 486 | /** 487 | * table-tab にクリックイベントを追加する 488 | * 489 | * @remarks 490 | * 491 | * table-tab とは、 class="table-tab" の div 要素。 492 | * 493 | * ABC や ARC に問題ページを切り替えるボタンのこと。 494 | */ 495 | export const make_table_tab_tag = async (): Promise => { 496 | const divs = document.getElementsByTagName("div"); 497 | 498 | /* table-tabのdiv要素を探す */ 499 | let target_div: HTMLDivElement|null = null; // table tabのdiv要素 500 | for (let i=0; idivs[i].firstChild; 502 | if (!divs[i].hasAttribute("role") || divs[i].firstChild===null || btn.innerText!=="ABC") continue; 503 | target_div = divs[i]; 504 | break; 505 | } 506 | 507 | if (target_div == null) { return; } 508 | 509 | /* table-tabの各子要素にクリックイベントを追加する */ 510 | for (let i=0; i { 15 | if(document.getElementById(consts.ID_SLA_ROOT) !== null){ return false; } 16 | const root_div = document.getElementById('root'); 17 | 18 | const target_div = root_div!.firstElementChild!.getElementsByClassName("container")[0].firstElementChild!; 19 | const insert_html = dom_ope.make_base_html(); 20 | target_div.insertBefore(insert_html, target_div.firstChild); 21 | 22 | await dom_ope.make_sla_table(); 23 | await dom_ope.make_checkboxes(); 24 | await clickjs.check_checkboxes(); 25 | await dom_ope.hilight_problems(); 26 | await dom_ope.make_table_tab_tag(); 27 | } 28 | -------------------------------------------------------------------------------- /solve-later-again/src/utils.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 指定時間スリープする 5 | * 6 | * @remarks 7 | * Promiseを返す 8 | * 9 | * @param time スリープする時間(ms) 10 | */ 11 | export const do_sleep = (time: number): Promise => { 12 | return new Promise((resolve, reject) => { 13 | setTimeout(() => { 14 | return resolve(); 15 | }, time); 16 | }); 17 | } 18 | 19 | 20 | /** 21 | * 文字列の日付をDate型に変換して返す 22 | * 23 | * @param dt_str 文字列の日付 24 | * 25 | * @example 26 | * ``` 27 | * strdate2date("2019/6/27(Thu)") 28 | * ``` 29 | * 30 | * @returns Date型の日付 31 | */ 32 | export const strdate2date = (dt_str: string) => { 33 | dt_str = dt_str.split('(')[0]; 34 | const dts = dt_str.split('/'); 35 | let dt = new Date; 36 | dt.setFullYear(Number(dts[0])); 37 | dt.setMonth(Number(dts[1])-1); 38 | dt.setDate(Number(dts[2])); 39 | return dt; 40 | } -------------------------------------------------------------------------------- /solve-later-again/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | "allowJs": false, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | // "outDir": "dst", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | }, 63 | "include": [ 64 | "src/**/*" 65 | ], 66 | "exclude": [ 67 | "node_modules" 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /solve-later-again/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | // const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | 4 | module.exports = { 5 | // モジュールバンドルを行う起点となるファイルの指定 6 | // 指定できる値としては、ファイル名の文字列や、それを並べた配列やオブジェクト 7 | // 下記はオブジェクトとして指定した例 8 | entry: { 9 | content_scripts: './src/content_scripts.ts', 10 | background: "./src/background.ts", 11 | }, 12 | output: { 13 | // モジュールバンドルを行った結果を出力する場所やファイル名の指定 14 | // "__dirname"はこのファイルが存在するディレクトリを表すnode.jsで定義済みの定数 15 | path: path.join(__dirname,'dst'), 16 | filename: 'bin/[name].js' // [name]は、entryのプロパティ名(content_scripts) 17 | }, 18 | // モジュールとして扱いたいファイルの拡張子を指定する 19 | // 例えば「import Foo from './foo'」という記述に対して"foo.ts"という名前のファイルをモジュールとして探す 20 | // デフォルトは['.js', '.json'] 21 | resolve: { 22 | extensions:['.ts','.tsx', '.js'] 23 | }, 24 | devServer: { 25 | // webpack-dev-serverの公開フォルダ 26 | contentBase: path.join(__dirname,'dst') 27 | }, 28 | // モジュールに適用するルールの設定(ここではローダーの設定を行う事が多い) 29 | module: { 30 | rules: [ 31 | { 32 | // 拡張子が.tsで終わるファイルに対して、TypeScriptコンパイラを適用する 33 | test:/\.(ts|tsx)$/, 34 | exclude: /node_modules/, 35 | use: { 36 | loader:'ts-loader' 37 | } 38 | }, 39 | { 40 | test: /\.scss$/, 41 | use: ['style-loader', 'css-loader', 'sass-loader'] 42 | } 43 | ] 44 | } 45 | } -------------------------------------------------------------------------------- /solve-later-again/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') // webpack-merge 2 | const common = require('./webpack.common.js') // 汎用設定をインポート 3 | 4 | // common設定とマージする 5 | module.exports = merge(common, { 6 | mode: 'development', // 開発モード 7 | devtool: 'inline-source-map', // 開発用ソースマップ 8 | optimization: { 9 | minimize: false // 出力JSファイルを圧縮しない 10 | } 11 | }) -------------------------------------------------------------------------------- /solve-later-again/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') // webpack-merge 2 | const common = require('./webpack.common.js') // 汎用設定をインポート 3 | 4 | // common設定とマージする 5 | module.exports = merge(common, { 6 | mode: 'production', // 本番モード 7 | optimization: { 8 | minimize: true // 出力JSファイルを圧縮する 9 | } 10 | }) --------------------------------------------------------------------------------