├── .github └── workflows │ └── npm-gulp.yml ├── License.md ├── README.md ├── dist └── genanki.js ├── docs ├── .nojekyll ├── CNAME ├── License.md ├── README.md ├── _navbar.md ├── _sidebar.md ├── demo │ ├── css │ │ └── demo.css │ ├── csv_to_apkg.js │ ├── demo.js │ ├── favicon.ico │ ├── index.html │ └── js │ │ ├── anki │ │ └── genanki.js │ │ ├── codemirror │ │ ├── codemirror.css │ │ ├── codemirror.js │ │ └── javascript.js │ │ ├── eruda.js │ │ ├── filesaver │ │ ├── FileSaver.min.js │ │ └── FileSaver.min.js.map │ │ ├── fsimage.js │ │ ├── index.js │ │ ├── jszip.min.js │ │ ├── sql │ │ └── sql.js │ │ └── test.js ├── documentation.md ├── examples.md └── index.html ├── genanki ├── anki_hash.js ├── apkg_col.js ├── apkg_schema.js ├── deck.js ├── default.js ├── license.js ├── model.js ├── note.js ├── package.js └── sha256.js ├── gulpfile.js ├── package.json ├── release.md └── sample ├── index.html └── js ├── anki └── genanki.js ├── filesaver ├── FileSaver.min.js └── FileSaver.min.js.map ├── index.js ├── jszip.min.js └── sql └── sql.js /.github/workflows/npm-gulp.yml: -------------------------------------------------------------------------------- 1 | name: Create release for genanki.js 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | version: 'v1.0.5' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [12.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Build 25 | run: | 26 | sudo apt install zip wget -q -y 27 | 28 | # bundle all files in genanki folder to dist, demo and sample folder 29 | sudo npm install gulp gulp-concat 30 | npm link gulp 31 | gulp bundleJs 32 | 33 | 34 | # add latest jszip to sample folder 35 | wget https://raw.githubusercontent.com/Stuk/jszip/master/dist/jszip.min.js \ 36 | -O sample/js/jszip.min.js 37 | 38 | # add latest FileSaver to sample folder 39 | wget https://raw.githubusercontent.com/eligrey/FileSaver.js/master/dist/FileSaver.min.js \ 40 | -O sample/js/filesaver/FileSaver.min.js 41 | 42 | wget https://raw.githubusercontent.com/eligrey/FileSaver.js/master/dist/FileSaver.min.js.map \ 43 | -O sample/js/filesaver/FileSaver.min.js.map 44 | 45 | # add latest sql.js to sample folder 46 | wget https://github.com/sql-js/sql.js/releases/download/v1.6.1/sql.js \ 47 | -O sample/js/sql/sql.js 48 | 49 | 50 | # add latest jszip to demo folder 51 | wget https://raw.githubusercontent.com/Stuk/jszip/master/dist/jszip.min.js \ 52 | -O docs/demo/js/jszip.min.js 53 | 54 | # add latest FileSaver to demp folder 55 | wget https://raw.githubusercontent.com/eligrey/FileSaver.js/master/dist/FileSaver.min.js \ 56 | -O docs/demo/js/filesaver/FileSaver.min.js 57 | 58 | wget https://raw.githubusercontent.com/eligrey/FileSaver.js/master/dist/FileSaver.min.js.map \ 59 | -O docs/demo/js/filesaver/FileSaver.min.js.map 60 | 61 | # add latest sql.js to demo folder 62 | wget https://github.com/sql-js/sql.js/releases/download/v1.6.1/sql.js \ 63 | -O docs/demo/js/sql/sql.js 64 | 65 | 66 | # remove node_modules and package.json generated when using gulp 67 | rm -rf node_modules 68 | rm -rf package-lock.json 69 | 70 | # commit changes 71 | git config --global user.name github-actions 72 | git config --global user.email github-actions@github.com 73 | 74 | if [ -n "$(git status --porcelain)" ]; then 75 | echo "there are changes"; 76 | git commit -am "${{ env.version }}" 77 | git push 78 | else 79 | echo "no changes"; 80 | fi 81 | 82 | 83 | # Create folder for release 84 | mkdir genanki-${{ env.version }} 85 | 86 | # copy new files 87 | cp -r dist genanki-${{ env.version }} 88 | cp -r sample genanki-${{ env.version }} 89 | 90 | zip -r genanki-${{ env.version }}.zip genanki-${{ env.version }} 91 | 92 | - name: Create Release 93 | uses: ncipollo/release-action@v1 94 | with: 95 | name: genanki-${{ env.version }} 96 | bodyFile: 'release.md' 97 | tag: ${{ env.version }} 98 | artifacts: genanki-${{ env.version }}.zip 99 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | ### [sql.js](https://github.com/sql-js/sql.js) 2 | [The MIT License](https://github.com/sql-js/sql.js/blob/master/LICENSE) 3 |
Copyright (c) 2017 sql.js authors (see AUTHORS) 4 | 5 | ### [JSZip](https://github.com/Stuk/jszip) 6 | JSZip is dual-licensed. You may use it under the MIT license or the GPLv3 license. See [JSZip LICENSE.markdown](https://github.com/Stuk/jszip/blob/master/LICENSE.markdown). 7 | 8 | ### [FileSaver.js](https://github.com/eligrey/FileSaver.js) 9 | [The MIT License](https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md) 10 |
Copyright © 2016 Eli Grey. 11 | 12 | ### [mkanki](https://github.com/nornagon/mkanki) 13 | [GNU Affero General Public License v3](https://opensource.org/licenses/AGPL-3.0) 14 |
Copyright (c) 2018 Jeremy Apthorp 15 | 16 | ### [js-sha256](https://github.com/emn178/js-sha256) 17 | [The MIT License](https://github.com/emn178/js-sha256/blob/master/LICENSE.txt) 18 |
Copyright (c) Chen, Yi-Cyuan 2017 19 | 20 | ### [genanki](https://github.com/kerrickstaley/genanki) 21 | [The MIT License](https://github.com/kerrickstaley/genanki/blob/master/LICENSE.txt) 22 |
Copyright (c) Kerrick Staley 2021 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # genanki-js 2 | A JavaScript implementation for generating Anki decks in browser. This is fork of [mkanki](https://github.com/nornagon/mkanki). 3 | 4 | # QuickStart 5 | Download [genanki](https://github.com/krmanik/genanki-js/releases) zip file from release pages. 6 | 7 | The zip file contains two folder 8 | 9 | - `dist` : This folder contains `genanki.js` file. If project already configured with [sql.js](https://github.com/sql-js/sql.js), [FileSaver.js](https://github.com/eligrey/FileSaver.js) and [JSZip](https://github.com/Stuk/jszip) then just add `genanki.js` file to project. 10 | 11 | - `sample` : This folder contains latest [sql.js](https://github.com/sql-js/sql.js), [FileSaver.js](https://github.com/eligrey/FileSaver.js), [JSZip](https://github.com/Stuk/jszip) and [genanki.js](https://github.com/krmanik/genanki-js). It is ready to use folder. 12 | 13 | Alternatively, `genanki.js` can also be loaded from CDN. 14 | ```html 15 | 16 | ``` 17 | 18 | # Documentation 19 | View [Documentation](https://krmanik.github.io/genanki-js/#/) 20 | 21 | # CSV/TSV to Anki Package 22 | Visit page and select `CSV to APKG` from top menu.
23 | [CSV to APKG](https://krmanik.github.io/genanki-js/demo/index.html#) 24 | 25 | # Set Up a new project from scratch 26 | 1. Download `genanki.js` from `dist` folder and add to the project 27 | ```html 28 | 29 | 30 | ``` 31 | 32 | 2. Add [sql.js](https://github.com/sql-js/sql.js), [FileSaver.js](https://github.com/eligrey/FileSaver.js) and [JSZip](https://github.com/Stuk/jszip) to the project 33 | 34 | >*Note: [mkanki](https://github.com/nornagon/mkanki) uses `better-sql`, `fs` and `archiver`, that make it difficult to be used in browser* 35 | 36 | ```html 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ``` 46 | 47 | 3. Create a `SQL` global variable (may be added to index.js) 48 | ```js 49 | // The `initSqlJs` function is globally provided by all of the main dist files if loaded in the browser. 50 | // We must specify this locateFile function if we are loading a wasm file from anywhere other than the current html page's folder. 51 | config = { 52 | locateFile: filename => `js/sql/sql-wasm.wasm` 53 | } 54 | 55 | var SQL; 56 | initSqlJs(config).then(function (sql) { 57 | //Create the database 58 | SQL = sql; 59 | }); 60 | ``` 61 | 62 | 4. Now use following `Examples` to generate and export decks. 63 | 64 | *View more examples here [Examples](https://krmanik.github.io/genanki-js/#/examples)* 65 | 66 | # Examples 67 | ```js 68 | var m = new Model({ 69 | name: "Basic (and reversed card)", 70 | id: "1543634829843", 71 | flds: [ 72 | { name: "Front" }, 73 | { name: "Back" } 74 | ], 75 | req: [ 76 | [ 0, "all", [ 0 ] ], 77 | [ 1, "all", [ 1 ] ] 78 | ], 79 | tmpls: [ 80 | { 81 | name: "Card 1", 82 | qfmt: "{{Front}}", 83 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 84 | }, 85 | { 86 | name: "Card 2", 87 | qfmt: "{{Back}}", 88 | afmt: "{{FrontSide}}\n\n
\n\n{{Front}}", 89 | } 90 | ], 91 | }) 92 | 93 | var d = new Deck(1276438724672, "Test Deck") 94 | 95 | d.addNote(m.note(['this is front', 'this is back'])) 96 | 97 | var p = new Package() 98 | p.addDeck(d) 99 | 100 | p.writeToFile('deck.apkg') 101 | ``` 102 | 103 | # License 104 | ### [genanki-js](https://github.com/krmanik/genanki-js) 105 | [GNU Affero General Public License v3](https://opensource.org/licenses/AGPL-3.0) 106 |
Copyright (c) 2021 Mani 107 | 108 | ## Other Third Party Licenses 109 | [License.md](https://github.com/krmanik/genanki-js/blob/master/License.md) 110 | -------------------------------------------------------------------------------- /dist/genanki.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [mkanki]{@link https://github.com/nornagon/mkanki} 3 | * @copyright Copyright (c) 2018 Jeremy Apthorp 4 | * @license AGPL-3.0 License 5 | * 6 | * 7 | * [genanki]{@link https://github.com/kerrickstaley/genanki} 8 | * @copyright Copyright (c) Kerrick Staley 2021 9 | * @license The MIT License 10 | * 11 | * 12 | * [genanki-js]{@link https://github.com/krmanik/genanki-js} 13 | * @copyright Copyright (c) 2021 Mani 14 | * @license AGPL-3.0 License 15 | */ 16 | 17 | BASE91_TABLE = [ 18 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 19 | 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 20 | 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', 21 | '5', '6', '7', '8', '9', '!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '-', '.', '/', ':', 22 | ';', '<', '=', '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~'] 23 | 24 | function ankiHash(fields) { 25 | const str = fields.join('__') 26 | const h = sha256.create(); 27 | h.update(str) 28 | const hex = h.digest() 29 | 30 | let hash_int = 0n 31 | for (let i = 0; i < 8; i++) { 32 | hash_int *= 256n 33 | hash_int += BigInt(hex[i]) 34 | } 35 | 36 | // convert to the weird base91 format that Anki uses 37 | let rv_reversed = [] 38 | while (hash_int > 0) { 39 | rv_reversed.push(BASE91_TABLE[hash_int % 91n]) 40 | hash_int = (hash_int / 91n) 41 | } 42 | 43 | return rv_reversed.reverse().join('') 44 | } 45 | 46 | const MODEL_STD = 0 47 | const MODEL_CLOZE = 1 48 | 49 | class Model { 50 | constructor(props) { 51 | this.props = { 52 | ...defaultModel, 53 | ...props, 54 | flds: props.flds.map((f, i) => ({ ...defaultField, ord: i, ...f })), 55 | tmpls: props.tmpls.map((t, i) => ({ ...defaultTemplate, ord: i, name: `Card ${i + 1}`, ...t })), 56 | mod: new Date().getTime() 57 | } 58 | this.fieldNameToOrd = {} 59 | this.props.flds.forEach(f => { this.fieldNameToOrd[f.name] = f.ord }) 60 | } 61 | 62 | note(fields, tags, guid = null) { 63 | if (Array.isArray(fields)) { 64 | if (fields.length !== this.props.flds.length) { 65 | throw new Error(`Expected ${this.props.flds.length} fields for model '${this.props.name}' but got ${fields.length}`) 66 | } 67 | return new Note(this, fields, tags, guid) 68 | } else { 69 | const field_names = Object.keys(fields) 70 | const fields_list = [] 71 | field_names.forEach(field_name => { 72 | const ord = this.fieldNameToOrd[field_name] 73 | if (ord == null) throw new Error(`Field '${field_name}' does not exist in the model`) 74 | fields_list[ord] = fields[field_name] 75 | }) 76 | return new Note(this, fields_list, tags, guid) 77 | } 78 | } 79 | } 80 | 81 | class ClozeModel extends Model { 82 | constructor(props) { 83 | super({ 84 | type: MODEL_CLOZE, 85 | css: ` 86 | .card { 87 | font-family: arial; 88 | font-size: 20px; 89 | text-align: center; 90 | color: black; 91 | background-color: white; 92 | } 93 | 94 | .cloze { 95 | font-weight: bold; 96 | color: blue; 97 | } 98 | `, 99 | tmpls: [{ name: "Cloze", ...props.tmpl }], 100 | ...props 101 | }) 102 | } 103 | } 104 | 105 | const defaultModel = { 106 | sortf: 0, // sort field 107 | did: 1, // deck id 108 | latexPre: `\\documentclass[12pt]{article} 109 | \\special{papersize=3in,5in} 110 | \\usepackage[utf8]{inputenc} 111 | \\usepackage{amssymb,amsmath} 112 | \\pagestyle{empty} 113 | \\setlength{\\parindent}{0in} 114 | \\begin{document}`, 115 | latexPost: "\\end{document}", 116 | mod: 0, // modification time 117 | usn: 0, // unsure, something to do with sync? 118 | vers: [], // seems to be unused 119 | type: MODEL_STD, 120 | css: `.card { 121 | font-family: arial; 122 | font-size: 20px; 123 | text-align: center; 124 | color: black; 125 | background-color: white; 126 | }`, 127 | /* also: 128 | name: string, 129 | flds: [Field], 130 | tmpls: [Template], 131 | tags: [??], 132 | id: string 133 | */ 134 | tags: [], 135 | } 136 | 137 | const defaultField = { 138 | name: "", 139 | ord: null, 140 | sticky: false, 141 | rtl: false, 142 | font: "Arial", 143 | size: 20, 144 | media: [], 145 | } 146 | 147 | const defaultTemplate = { 148 | name: "", 149 | ord: null, 150 | qfmt: "", 151 | afmt: "", 152 | did: null, 153 | bqfmt: "", 154 | bafmt: "", 155 | } 156 | 157 | // whether new cards should be mixed with reviews, or shown first or last 158 | const NEW_CARDS_DISTRIBUTE = 0 159 | const NEW_CARDS_LAST = 1 160 | const NEW_CARDS_FIRST = 2 161 | 162 | const defaultConf = { 163 | // review options 164 | 'activeDecks': [1], 165 | 'curDeck': 1, 166 | 'newSpread': NEW_CARDS_DISTRIBUTE, 167 | 'collapseTime': 1200, 168 | 'timeLim': 0, 169 | 'estTimes': true, 170 | 'dueCounts': true, 171 | // other config 172 | 'curModel': null, 173 | 'nextPos': 1, 174 | 'sortType': "noteFld", 175 | 'sortBackwards': false, 176 | 'addToCur': true, // add new to currently selected deck? 177 | 'dayLearnFirst': false, 178 | } 179 | 180 | 181 | // new card insertion order 182 | const NEW_CARDS_RANDOM = 0 183 | const NEW_CARDS_DUE = 1 184 | 185 | const STARTING_FACTOR = 2500 186 | 187 | const defaultDeckConf = { 188 | 'name': "Default", 189 | 'new': { 190 | 'delays': [1, 10], 191 | 'ints': [1, 4, 7], // 7 is not currently used 192 | 'initialFactor': STARTING_FACTOR, 193 | 'separate': true, 194 | 'order': NEW_CARDS_DUE, 195 | 'perDay': 20, 196 | // may not be set on old decks 197 | 'bury': false, 198 | }, 199 | 'lapse': { 200 | 'delays': [10], 201 | 'mult': 0, 202 | 'minInt': 1, 203 | 'leechFails': 8, 204 | // type 0=suspend, 1=tagonly 205 | 'leechAction': 0, 206 | }, 207 | 'rev': { 208 | 'perDay': 200, 209 | 'ease4': 1.3, 210 | 'fuzz': 0.05, 211 | 'minSpace': 1, // not currently used 212 | 'ivlFct': 1, 213 | 'maxIvl': 36500, 214 | // may not be set on old decks 215 | 'bury': false, 216 | 'hardFactor': 1.2, 217 | }, 218 | 'maxTaken': 60, 219 | 'timer': 0, 220 | 'autoplay': true, 221 | 'replayq': true, 222 | 'mod': 0, 223 | 'usn': 0, 224 | } 225 | 226 | const defaultDeck = { 227 | newToday: [0, 0], // currentDay, count 228 | revToday: [0, 0], 229 | lrnToday: [0, 0], 230 | timeToday: [0, 0], // time in ms 231 | conf: 1, 232 | usn: 0, 233 | desc: "", 234 | dyn: 0, // anki uses int/bool interchangably here 235 | collapsed: false, 236 | // added in beta11 237 | extendNew: 10, 238 | extendRev: 50, 239 | } 240 | 241 | class Deck { 242 | constructor(id, name, desc="") { 243 | this.id = id 244 | this.name = name 245 | this.desc = desc 246 | this.notes = [] 247 | } 248 | 249 | addNote(note) { 250 | this.notes.push(note) 251 | } 252 | } 253 | 254 | class Note { 255 | constructor(model, fields, tags = null, guid = null) { 256 | this.model = model 257 | this.fields = fields 258 | this.tags = tags 259 | this._guid = guid 260 | } 261 | 262 | get guid() { 263 | return this._guid ? this._guid : ankiHash(this.fields); 264 | } 265 | 266 | get cards() { 267 | if (this.model.props.type === MODEL_STD) { 268 | const isEmpty = f => { 269 | return !f || f.toString().trim().length === 0 270 | } 271 | const rv = [] 272 | for (const [card_ord, any_or_all, required_field_ords] of this.model.props.req) { 273 | const op = any_or_all === "any" ? "some" : "every" 274 | if (required_field_ords[op](f => !isEmpty(this.fields[f]))) { 275 | rv.push(card_ord) 276 | } 277 | } 278 | return rv 279 | } else { 280 | // the below logic is copied from anki's ModelManager._availClozeOrds 281 | const ords = new Set() 282 | const matches = [] 283 | const curliesRe = /{{[^}]*?cloze:(?:[^}]?:)*(.+?)}}/g 284 | const percentRe = /<%cloze:(.+?)%>/g 285 | const { qfmt } = this.model.props.tmpls[0] // cloze models only have 1 template 286 | let m; 287 | while (m = curliesRe.exec(qfmt)) 288 | matches.push(m[1]) 289 | while (m = percentRe.exec(qfmt)) 290 | matches.push(m[1]) 291 | const map = {} 292 | this.model.props.flds.forEach((fld, i) => { 293 | map[fld.name] = [i, fld] 294 | }) 295 | for (const fname of matches) { 296 | if (!(fname in map)) continue 297 | const ord = map[fname][0] 298 | const re = /{{c(\d+)::.+?}}/gs 299 | while (m = re.exec(this.fields[ord])) { 300 | const i = parseInt(m[1]) 301 | if (i > 0) 302 | ords.add(i - 1) 303 | } 304 | } 305 | if (ords.size === 0) { 306 | // empty clozes use first ord 307 | return [0] 308 | } 309 | return Array.from(ords) 310 | } 311 | } 312 | } 313 | 314 | class Package { 315 | constructor() { 316 | this.decks = [] 317 | this.media = [] 318 | } 319 | 320 | addDeck(deck) { 321 | this.decks.push(deck) 322 | } 323 | 324 | addMedia(data, name) { 325 | this.media.push({ name, data }) 326 | } 327 | 328 | addMediaFile(filename, name = null) { 329 | this.media.push({ name: name || filename, filename }) 330 | } 331 | 332 | writeToFile(filename) { 333 | var db = new SQL.Database(); 334 | db.run(APKG_SCHEMA); 335 | 336 | this.write(db) 337 | 338 | var zip = new JSZip(); 339 | 340 | const data = db.export(); 341 | const buffer = new Uint8Array(data).buffer; 342 | 343 | zip.file("collection.anki2", buffer); 344 | 345 | const media_info = {} 346 | 347 | this.media.forEach((m, i) => { 348 | if (m.filename != null) { 349 | zip.file(i.toString(), m.filename) 350 | } else { 351 | zip.file(i.toString(), m.data) 352 | } 353 | 354 | media_info[i] = m.name 355 | }) 356 | 357 | zip.file('media', JSON.stringify(media_info)) 358 | 359 | zip.generateAsync({ type: "blob", mimeType: "application/apkg" }).then(function (content) { 360 | // see FileSaver.js 361 | saveAs(content, filename); 362 | }); 363 | } 364 | 365 | 366 | write(db) { 367 | const now = new Date 368 | const models = {} 369 | const decks = {} 370 | 371 | // AnkiDroid failed to import subdeck, So add a Default deck 372 | decks["1"] = {...defaultDeck, id: 1, name: "Default"} 373 | 374 | this.decks.forEach(d => { 375 | d.notes.forEach(n => models[n.model.props.id] = n.model.props) 376 | decks[d.id] = { 377 | ...defaultDeck, 378 | id: d.id, 379 | name: d.name, 380 | desc: d.desc, 381 | } 382 | }) 383 | 384 | const col = [ 385 | null, // id 386 | (+now / 1000) | 0, // crt 387 | +now, // mod 388 | +now, // scm 389 | 11, // ver 390 | 0, // dty 391 | 0, // usn 392 | 0, // ls 393 | JSON.stringify(defaultConf), // conf 394 | JSON.stringify(models), // models 395 | JSON.stringify(decks), // decks 396 | JSON.stringify({ 1: { id: 1, ...defaultDeckConf } }), // dconf 397 | JSON.stringify({}), // tags 398 | ] 399 | 400 | db.prepare(`INSERT INTO col 401 | (id, crt, mod, scm, ver, dty, usn, ls, conf, models, decks, dconf, tags) 402 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(col) 403 | 404 | const insert_notes = db.prepare(`INSERT INTO notes (id, guid, mid, mod, usn, tags, flds, sfld, csum, flags, data) 405 | VALUES (null, ?, ?, ?, ?, ?, ?, ?, 0, 0, '')`) 406 | 407 | const insert_cards = db.prepare(`INSERT INTO cards (id, nid, did, ord, mod, usn, type, queue, due, ivl, factor, reps, lapses, left, odue, odid, flags, data) 408 | VALUES (null, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, 0, 0, 0, 0, 0, 0, '')`) 409 | 410 | for (const deck of this.decks) { 411 | for (const note of deck.notes) { 412 | var tags = note.tags == null ? '' : note.tags.join(' ') 413 | insert_notes.run( 414 | [ 415 | note.guid, // guid 416 | note.model.props.id, // mid 417 | (+now / 1000) | 0, // mod 418 | -1, // usn 419 | tags, // tags 420 | note.fields.join('\x1f'), // flds 421 | 0, // sfld 422 | ]) 423 | 424 | var rowID = db.exec("select last_insert_rowid();") 425 | var note_id = rowID[0]['values'][0][0]; 426 | 427 | for (const card_ord of note.cards) { 428 | insert_cards.run( 429 | [ 430 | note_id, // nid 431 | deck.id, // did 432 | card_ord, // ord 433 | (+now / 1000) | 0, // mod 434 | -1, // usn 435 | 0, // type 0=new, 1=learning, 2=due 436 | 0, // queue -1 for suspended 437 | ]) 438 | } 439 | } 440 | } 441 | } 442 | } 443 | 444 | /** 445 | * [js-sha256]{@link https://github.com/emn178/js-sha256} 446 | * 447 | * @version 0.9.0 448 | * @author Chen, Yi-Cyuan [emn178@gmail.com] 449 | * @copyright Chen, Yi-Cyuan 2014-2017 450 | * @license MIT 451 | */ 452 | /*jslint bitwise: true */ 453 | (function () { 454 | 'use strict'; 455 | 456 | var ERROR = 'input is invalid type'; 457 | var WINDOW = typeof window === 'object'; 458 | var root = WINDOW ? window : {}; 459 | if (root.JS_SHA256_NO_WINDOW) { 460 | WINDOW = false; 461 | } 462 | var WEB_WORKER = !WINDOW && typeof self === 'object'; 463 | var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; 464 | if (NODE_JS) { 465 | root = global; 466 | } else if (WEB_WORKER) { 467 | root = self; 468 | } 469 | var COMMON_JS = !root.JS_SHA256_NO_COMMON_JS && typeof module === 'object' && module.exports; 470 | var AMD = typeof define === 'function' && define.amd; 471 | var ARRAY_BUFFER = !root.JS_SHA256_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; 472 | var HEX_CHARS = '0123456789abcdef'.split(''); 473 | var EXTRA = [-2147483648, 8388608, 32768, 128]; 474 | var SHIFT = [24, 16, 8, 0]; 475 | var K = [ 476 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 477 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 478 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 479 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 480 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 481 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 482 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 483 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 484 | ]; 485 | var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer']; 486 | 487 | var blocks = []; 488 | 489 | if (root.JS_SHA256_NO_NODE_JS || !Array.isArray) { 490 | Array.isArray = function (obj) { 491 | return Object.prototype.toString.call(obj) === '[object Array]'; 492 | }; 493 | } 494 | 495 | if (ARRAY_BUFFER && (root.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) { 496 | ArrayBuffer.isView = function (obj) { 497 | return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; 498 | }; 499 | } 500 | 501 | var createOutputMethod = function (outputType, is224) { 502 | return function (message) { 503 | return new Sha256(is224, true).update(message)[outputType](); 504 | }; 505 | }; 506 | 507 | var createMethod = function (is224) { 508 | var method = createOutputMethod('hex', is224); 509 | if (NODE_JS) { 510 | method = nodeWrap(method, is224); 511 | } 512 | method.create = function () { 513 | return new Sha256(is224); 514 | }; 515 | method.update = function (message) { 516 | return method.create().update(message); 517 | }; 518 | for (var i = 0; i < OUTPUT_TYPES.length; ++i) { 519 | var type = OUTPUT_TYPES[i]; 520 | method[type] = createOutputMethod(type, is224); 521 | } 522 | return method; 523 | }; 524 | 525 | var nodeWrap = function (method, is224) { 526 | var crypto = eval("require('crypto')"); 527 | var Buffer = eval("require('buffer').Buffer"); 528 | var algorithm = is224 ? 'sha224' : 'sha256'; 529 | var nodeMethod = function (message) { 530 | if (typeof message === 'string') { 531 | return crypto.createHash(algorithm).update(message, 'utf8').digest('hex'); 532 | } else { 533 | if (message === null || message === undefined) { 534 | throw new Error(ERROR); 535 | } else if (message.constructor === ArrayBuffer) { 536 | message = new Uint8Array(message); 537 | } 538 | } 539 | if (Array.isArray(message) || ArrayBuffer.isView(message) || 540 | message.constructor === Buffer) { 541 | return crypto.createHash(algorithm).update(new Buffer(message)).digest('hex'); 542 | } else { 543 | return method(message); 544 | } 545 | }; 546 | return nodeMethod; 547 | }; 548 | 549 | var createHmacOutputMethod = function (outputType, is224) { 550 | return function (key, message) { 551 | return new HmacSha256(key, is224, true).update(message)[outputType](); 552 | }; 553 | }; 554 | 555 | var createHmacMethod = function (is224) { 556 | var method = createHmacOutputMethod('hex', is224); 557 | method.create = function (key) { 558 | return new HmacSha256(key, is224); 559 | }; 560 | method.update = function (key, message) { 561 | return method.create(key).update(message); 562 | }; 563 | for (var i = 0; i < OUTPUT_TYPES.length; ++i) { 564 | var type = OUTPUT_TYPES[i]; 565 | method[type] = createHmacOutputMethod(type, is224); 566 | } 567 | return method; 568 | }; 569 | 570 | function Sha256(is224, sharedMemory) { 571 | if (sharedMemory) { 572 | blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = 573 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 574 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 575 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 576 | this.blocks = blocks; 577 | } else { 578 | this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 579 | } 580 | 581 | if (is224) { 582 | this.h0 = 0xc1059ed8; 583 | this.h1 = 0x367cd507; 584 | this.h2 = 0x3070dd17; 585 | this.h3 = 0xf70e5939; 586 | this.h4 = 0xffc00b31; 587 | this.h5 = 0x68581511; 588 | this.h6 = 0x64f98fa7; 589 | this.h7 = 0xbefa4fa4; 590 | } else { // 256 591 | this.h0 = 0x6a09e667; 592 | this.h1 = 0xbb67ae85; 593 | this.h2 = 0x3c6ef372; 594 | this.h3 = 0xa54ff53a; 595 | this.h4 = 0x510e527f; 596 | this.h5 = 0x9b05688c; 597 | this.h6 = 0x1f83d9ab; 598 | this.h7 = 0x5be0cd19; 599 | } 600 | 601 | this.block = this.start = this.bytes = this.hBytes = 0; 602 | this.finalized = this.hashed = false; 603 | this.first = true; 604 | this.is224 = is224; 605 | } 606 | 607 | Sha256.prototype.update = function (message) { 608 | if (this.finalized) { 609 | return; 610 | } 611 | var notString, type = typeof message; 612 | if (type !== 'string') { 613 | if (type === 'object') { 614 | if (message === null) { 615 | throw new Error(ERROR); 616 | } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { 617 | message = new Uint8Array(message); 618 | } else if (!Array.isArray(message)) { 619 | if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { 620 | throw new Error(ERROR); 621 | } 622 | } 623 | } else { 624 | throw new Error(ERROR); 625 | } 626 | notString = true; 627 | } 628 | var code, index = 0, i, length = message.length, blocks = this.blocks; 629 | 630 | while (index < length) { 631 | if (this.hashed) { 632 | this.hashed = false; 633 | blocks[0] = this.block; 634 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = 635 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 636 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 637 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 638 | } 639 | 640 | if (notString) { 641 | for (i = this.start; index < length && i < 64; ++index) { 642 | blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; 643 | } 644 | } else { 645 | for (i = this.start; index < length && i < 64; ++index) { 646 | code = message.charCodeAt(index); 647 | if (code < 0x80) { 648 | blocks[i >> 2] |= code << SHIFT[i++ & 3]; 649 | } else if (code < 0x800) { 650 | blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; 651 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 652 | } else if (code < 0xd800 || code >= 0xe000) { 653 | blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; 654 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 655 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 656 | } else { 657 | code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); 658 | blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; 659 | blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; 660 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 661 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 662 | } 663 | } 664 | } 665 | 666 | this.lastByteIndex = i; 667 | this.bytes += i - this.start; 668 | if (i >= 64) { 669 | this.block = blocks[16]; 670 | this.start = i - 64; 671 | this.hash(); 672 | this.hashed = true; 673 | } else { 674 | this.start = i; 675 | } 676 | } 677 | if (this.bytes > 4294967295) { 678 | this.hBytes += this.bytes / 4294967296 << 0; 679 | this.bytes = this.bytes % 4294967296; 680 | } 681 | return this; 682 | }; 683 | 684 | Sha256.prototype.finalize = function () { 685 | if (this.finalized) { 686 | return; 687 | } 688 | this.finalized = true; 689 | var blocks = this.blocks, i = this.lastByteIndex; 690 | blocks[16] = this.block; 691 | blocks[i >> 2] |= EXTRA[i & 3]; 692 | this.block = blocks[16]; 693 | if (i >= 56) { 694 | if (!this.hashed) { 695 | this.hash(); 696 | } 697 | blocks[0] = this.block; 698 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = 699 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 700 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 701 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 702 | } 703 | blocks[14] = this.hBytes << 3 | this.bytes >>> 29; 704 | blocks[15] = this.bytes << 3; 705 | this.hash(); 706 | }; 707 | 708 | Sha256.prototype.hash = function () { 709 | var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4, f = this.h5, g = this.h6, 710 | h = this.h7, blocks = this.blocks, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc; 711 | 712 | for (j = 16; j < 64; ++j) { 713 | // rightrotate 714 | t1 = blocks[j - 15]; 715 | s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); 716 | t1 = blocks[j - 2]; 717 | s1 = ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10); 718 | blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0; 719 | } 720 | 721 | bc = b & c; 722 | for (j = 0; j < 64; j += 4) { 723 | if (this.first) { 724 | if (this.is224) { 725 | ab = 300032; 726 | t1 = blocks[0] - 1413257819; 727 | h = t1 - 150054599 << 0; 728 | d = t1 + 24177077 << 0; 729 | } else { 730 | ab = 704751109; 731 | t1 = blocks[0] - 210244248; 732 | h = t1 - 1521486534 << 0; 733 | d = t1 + 143694565 << 0; 734 | } 735 | this.first = false; 736 | } else { 737 | s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)); 738 | s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)); 739 | ab = a & b; 740 | maj = ab ^ (a & c) ^ bc; 741 | ch = (e & f) ^ (~e & g); 742 | t1 = h + s1 + ch + K[j] + blocks[j]; 743 | t2 = s0 + maj; 744 | h = d + t1 << 0; 745 | d = t1 + t2 << 0; 746 | } 747 | s0 = ((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10)); 748 | s1 = ((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7)); 749 | da = d & a; 750 | maj = da ^ (d & b) ^ ab; 751 | ch = (h & e) ^ (~h & f); 752 | t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; 753 | t2 = s0 + maj; 754 | g = c + t1 << 0; 755 | c = t1 + t2 << 0; 756 | s0 = ((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10)); 757 | s1 = ((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7)); 758 | cd = c & d; 759 | maj = cd ^ (c & a) ^ da; 760 | ch = (g & h) ^ (~g & e); 761 | t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; 762 | t2 = s0 + maj; 763 | f = b + t1 << 0; 764 | b = t1 + t2 << 0; 765 | s0 = ((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10)); 766 | s1 = ((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7)); 767 | bc = b & c; 768 | maj = bc ^ (b & d) ^ cd; 769 | ch = (f & g) ^ (~f & h); 770 | t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; 771 | t2 = s0 + maj; 772 | e = a + t1 << 0; 773 | a = t1 + t2 << 0; 774 | } 775 | 776 | this.h0 = this.h0 + a << 0; 777 | this.h1 = this.h1 + b << 0; 778 | this.h2 = this.h2 + c << 0; 779 | this.h3 = this.h3 + d << 0; 780 | this.h4 = this.h4 + e << 0; 781 | this.h5 = this.h5 + f << 0; 782 | this.h6 = this.h6 + g << 0; 783 | this.h7 = this.h7 + h << 0; 784 | }; 785 | 786 | Sha256.prototype.hex = function () { 787 | this.finalize(); 788 | 789 | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, 790 | h6 = this.h6, h7 = this.h7; 791 | 792 | var hex = HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] + 793 | HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + 794 | HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + 795 | HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + 796 | HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + 797 | HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + 798 | HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + 799 | HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + 800 | HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + 801 | HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + 802 | HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + 803 | HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + 804 | HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] + 805 | HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + 806 | HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + 807 | HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + 808 | HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] + 809 | HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] + 810 | HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] + 811 | HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F] + 812 | HEX_CHARS[(h5 >> 28) & 0x0F] + HEX_CHARS[(h5 >> 24) & 0x0F] + 813 | HEX_CHARS[(h5 >> 20) & 0x0F] + HEX_CHARS[(h5 >> 16) & 0x0F] + 814 | HEX_CHARS[(h5 >> 12) & 0x0F] + HEX_CHARS[(h5 >> 8) & 0x0F] + 815 | HEX_CHARS[(h5 >> 4) & 0x0F] + HEX_CHARS[h5 & 0x0F] + 816 | HEX_CHARS[(h6 >> 28) & 0x0F] + HEX_CHARS[(h6 >> 24) & 0x0F] + 817 | HEX_CHARS[(h6 >> 20) & 0x0F] + HEX_CHARS[(h6 >> 16) & 0x0F] + 818 | HEX_CHARS[(h6 >> 12) & 0x0F] + HEX_CHARS[(h6 >> 8) & 0x0F] + 819 | HEX_CHARS[(h6 >> 4) & 0x0F] + HEX_CHARS[h6 & 0x0F]; 820 | if (!this.is224) { 821 | hex += HEX_CHARS[(h7 >> 28) & 0x0F] + HEX_CHARS[(h7 >> 24) & 0x0F] + 822 | HEX_CHARS[(h7 >> 20) & 0x0F] + HEX_CHARS[(h7 >> 16) & 0x0F] + 823 | HEX_CHARS[(h7 >> 12) & 0x0F] + HEX_CHARS[(h7 >> 8) & 0x0F] + 824 | HEX_CHARS[(h7 >> 4) & 0x0F] + HEX_CHARS[h7 & 0x0F]; 825 | } 826 | return hex; 827 | }; 828 | 829 | Sha256.prototype.toString = Sha256.prototype.hex; 830 | 831 | Sha256.prototype.digest = function () { 832 | this.finalize(); 833 | 834 | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, 835 | h6 = this.h6, h7 = this.h7; 836 | 837 | var arr = [ 838 | (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF, 839 | (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF, 840 | (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF, 841 | (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF, 842 | (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF, 843 | (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, h5 & 0xFF, 844 | (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, h6 & 0xFF 845 | ]; 846 | if (!this.is224) { 847 | arr.push((h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, h7 & 0xFF); 848 | } 849 | return arr; 850 | }; 851 | 852 | Sha256.prototype.array = Sha256.prototype.digest; 853 | 854 | Sha256.prototype.arrayBuffer = function () { 855 | this.finalize(); 856 | 857 | var buffer = new ArrayBuffer(this.is224 ? 28 : 32); 858 | var dataView = new DataView(buffer); 859 | dataView.setUint32(0, this.h0); 860 | dataView.setUint32(4, this.h1); 861 | dataView.setUint32(8, this.h2); 862 | dataView.setUint32(12, this.h3); 863 | dataView.setUint32(16, this.h4); 864 | dataView.setUint32(20, this.h5); 865 | dataView.setUint32(24, this.h6); 866 | if (!this.is224) { 867 | dataView.setUint32(28, this.h7); 868 | } 869 | return buffer; 870 | }; 871 | 872 | function HmacSha256(key, is224, sharedMemory) { 873 | var i, type = typeof key; 874 | if (type === 'string') { 875 | var bytes = [], length = key.length, index = 0, code; 876 | for (i = 0; i < length; ++i) { 877 | code = key.charCodeAt(i); 878 | if (code < 0x80) { 879 | bytes[index++] = code; 880 | } else if (code < 0x800) { 881 | bytes[index++] = (0xc0 | (code >> 6)); 882 | bytes[index++] = (0x80 | (code & 0x3f)); 883 | } else if (code < 0xd800 || code >= 0xe000) { 884 | bytes[index++] = (0xe0 | (code >> 12)); 885 | bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); 886 | bytes[index++] = (0x80 | (code & 0x3f)); 887 | } else { 888 | code = 0x10000 + (((code & 0x3ff) << 10) | (key.charCodeAt(++i) & 0x3ff)); 889 | bytes[index++] = (0xf0 | (code >> 18)); 890 | bytes[index++] = (0x80 | ((code >> 12) & 0x3f)); 891 | bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); 892 | bytes[index++] = (0x80 | (code & 0x3f)); 893 | } 894 | } 895 | key = bytes; 896 | } else { 897 | if (type === 'object') { 898 | if (key === null) { 899 | throw new Error(ERROR); 900 | } else if (ARRAY_BUFFER && key.constructor === ArrayBuffer) { 901 | key = new Uint8Array(key); 902 | } else if (!Array.isArray(key)) { 903 | if (!ARRAY_BUFFER || !ArrayBuffer.isView(key)) { 904 | throw new Error(ERROR); 905 | } 906 | } 907 | } else { 908 | throw new Error(ERROR); 909 | } 910 | } 911 | 912 | if (key.length > 64) { 913 | key = (new Sha256(is224, true)).update(key).array(); 914 | } 915 | 916 | var oKeyPad = [], iKeyPad = []; 917 | for (i = 0; i < 64; ++i) { 918 | var b = key[i] || 0; 919 | oKeyPad[i] = 0x5c ^ b; 920 | iKeyPad[i] = 0x36 ^ b; 921 | } 922 | 923 | Sha256.call(this, is224, sharedMemory); 924 | 925 | this.update(iKeyPad); 926 | this.oKeyPad = oKeyPad; 927 | this.inner = true; 928 | this.sharedMemory = sharedMemory; 929 | } 930 | HmacSha256.prototype = new Sha256(); 931 | 932 | HmacSha256.prototype.finalize = function () { 933 | Sha256.prototype.finalize.call(this); 934 | if (this.inner) { 935 | this.inner = false; 936 | var innerHash = this.array(); 937 | Sha256.call(this, this.is224, this.sharedMemory); 938 | this.update(this.oKeyPad); 939 | this.update(innerHash); 940 | Sha256.prototype.finalize.call(this); 941 | } 942 | }; 943 | 944 | var exports = createMethod(); 945 | exports.sha256 = exports; 946 | exports.sha224 = createMethod(true); 947 | exports.sha256.hmac = createHmacMethod(); 948 | exports.sha224.hmac = createHmacMethod(true); 949 | 950 | if (COMMON_JS) { 951 | module.exports = exports; 952 | } else { 953 | root.sha256 = exports.sha256; 954 | root.sha224 = exports.sha224; 955 | if (AMD) { 956 | define(function () { 957 | return exports; 958 | }); 959 | } 960 | } 961 | })(); 962 | 963 | /* 964 | * [genanki]{@link https://github.com/kerrickstaley/genanki} 965 | * @copyright Copyright (c) Kerrick Staley 2021 966 | * @license The MIT License 967 | */ 968 | 969 | const APKG_SCHEMA = ` 970 | PRAGMA foreign_keys=OFF; 971 | BEGIN TRANSACTION; 972 | 973 | CREATE TABLE col ( 974 | id integer primary key, 975 | crt integer not null, 976 | mod integer not null, 977 | scm integer not null, 978 | ver integer not null, 979 | dty integer not null, 980 | usn integer not null, 981 | ls integer not null, 982 | conf text not null, 983 | models text not null, 984 | decks text not null, 985 | dconf text not null, 986 | tags text not null 987 | ); 988 | CREATE TABLE notes ( 989 | id integer primary key, /* 0 */ 990 | guid text not null, /* 1 */ 991 | mid integer not null, /* 2 */ 992 | mod integer not null, /* 3 */ 993 | usn integer not null, /* 4 */ 994 | tags text not null, /* 5 */ 995 | flds text not null, /* 6 */ 996 | sfld integer not null, /* 7 */ 997 | csum integer not null, /* 8 */ 998 | flags integer not null, /* 9 */ 999 | data text not null /* 10 */ 1000 | ); 1001 | CREATE TABLE cards ( 1002 | id integer primary key, /* 0 */ 1003 | nid integer not null, /* 1 */ 1004 | did integer not null, /* 2 */ 1005 | ord integer not null, /* 3 */ 1006 | mod integer not null, /* 4 */ 1007 | usn integer not null, /* 5 */ 1008 | type integer not null, /* 6 */ 1009 | queue integer not null, /* 7 */ 1010 | due integer not null, /* 8 */ 1011 | ivl integer not null, /* 9 */ 1012 | factor integer not null, /* 10 */ 1013 | reps integer not null, /* 11 */ 1014 | lapses integer not null, /* 12 */ 1015 | left integer not null, /* 13 */ 1016 | odue integer not null, /* 14 */ 1017 | odid integer not null, /* 15 */ 1018 | flags integer not null, /* 16 */ 1019 | data text not null /* 17 */ 1020 | ); 1021 | CREATE TABLE revlog ( 1022 | id integer primary key, 1023 | cid integer not null, 1024 | usn integer not null, 1025 | ease integer not null, 1026 | ivl integer not null, 1027 | lastIvl integer not null, 1028 | factor integer not null, 1029 | time integer not null, 1030 | type integer not null 1031 | ); 1032 | CREATE TABLE graves ( 1033 | usn integer not null, 1034 | oid integer not null, 1035 | type integer not null 1036 | ); 1037 | CREATE INDEX ix_notes_usn on notes (usn); 1038 | CREATE INDEX ix_cards_usn on cards (usn); 1039 | CREATE INDEX ix_revlog_usn on revlog (usn); 1040 | CREATE INDEX ix_cards_nid on cards (nid); 1041 | CREATE INDEX ix_cards_sched on cards (did, queue, due); 1042 | CREATE INDEX ix_revlog_cid on revlog (cid); 1043 | CREATE INDEX ix_notes_csum on notes (csum); 1044 | COMMIT; 1045 | `; 1046 | 1047 | var APKG_COL = ` 1048 | INSERT INTO col VALUES( 1049 | null, 1050 | 1411124400, 1051 | 1425279151694, 1052 | 1425279151690, 1053 | 11, 1054 | 0, 1055 | 0, 1056 | 0, 1057 | '{ 1058 | "activeDecks": [ 1059 | 1 1060 | ], 1061 | "addToCur": true, 1062 | "collapseTime": 1200, 1063 | "curDeck": 1, 1064 | "curModel": "1425279151691", 1065 | "dueCounts": true, 1066 | "estTimes": true, 1067 | "newBury": true, 1068 | "newSpread": 0, 1069 | "nextPos": 1, 1070 | "sortBackwards": false, 1071 | "sortType": "noteFld", 1072 | "timeLim": 0 1073 | }', 1074 | '{}', 1075 | '{ 1076 | "1": { 1077 | "collapsed": false, 1078 | "conf": 1, 1079 | "desc": "", 1080 | "dyn": 0, 1081 | "extendNew": 10, 1082 | "extendRev": 50, 1083 | "id": 1, 1084 | "lrnToday": [ 1085 | 0, 1086 | 0 1087 | ], 1088 | "mod": 1425279151, 1089 | "name": "Default", 1090 | "newToday": [ 1091 | 0, 1092 | 0 1093 | ], 1094 | "revToday": [ 1095 | 0, 1096 | 0 1097 | ], 1098 | "timeToday": [ 1099 | 0, 1100 | 0 1101 | ], 1102 | "usn": 0 1103 | } 1104 | }', 1105 | '{ 1106 | "1": { 1107 | "autoplay": true, 1108 | "id": 1, 1109 | "lapse": { 1110 | "delays": [ 1111 | 10 1112 | ], 1113 | "leechAction": 0, 1114 | "leechFails": 8, 1115 | "minInt": 1, 1116 | "mult": 0 1117 | }, 1118 | "maxTaken": 60, 1119 | "mod": 0, 1120 | "name": "Default", 1121 | "new": { 1122 | "bury": true, 1123 | "delays": [ 1124 | 1, 1125 | 10 1126 | ], 1127 | "initialFactor": 2500, 1128 | "ints": [ 1129 | 1, 1130 | 4, 1131 | 7 1132 | ], 1133 | "order": 1, 1134 | "perDay": 20, 1135 | "separate": true 1136 | }, 1137 | "replayq": true, 1138 | "rev": { 1139 | "bury": true, 1140 | "ease4": 1.3, 1141 | "fuzz": 0.05, 1142 | "ivlFct": 1, 1143 | "maxIvl": 36500, 1144 | "minSpace": 1, 1145 | "perDay": 100 1146 | }, 1147 | "timer": 0, 1148 | "usn": 0 1149 | } 1150 | }', 1151 | '{}' 1152 | ); 1153 | `; 1154 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krmanik/genanki-js/7a88bc5d3a22ae375309080daffc36f4e11e51cd/docs/.nojekyll -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | genanki.js.org -------------------------------------------------------------------------------- /docs/License.md: -------------------------------------------------------------------------------- 1 | ### [sql.js](https://github.com/sql-js/sql.js) 2 | [The MIT License](https://github.com/sql-js/sql.js/blob/master/LICENSE) 3 |
Copyright (c) 2017 sql.js authors (see AUTHORS) 4 | 5 | ### [JSZip](https://github.com/Stuk/jszip) 6 | JSZip is dual-licensed. You may use it under the MIT license or the GPLv3 license. See [JSZip LICENSE.markdown](https://github.com/Stuk/jszip/blob/master/LICENSE.markdown). 7 | 8 | ### [FileSaver.js](https://github.com/eligrey/FileSaver.js) 9 | [The MIT License](https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md) 10 |
Copyright © 2016 Eli Grey. 11 | 12 | ### [mkanki](https://github.com/nornagon/mkanki) 13 | [GNU Affero General Public License v3](https://opensource.org/licenses/AGPL-3.0) 14 |
Copyright (c) 2018 Jeremy Apthorp 15 | 16 | ### [js-sha256](https://github.com/emn178/js-sha256) 17 | [The MIT License](https://github.com/emn178/js-sha256/blob/master/LICENSE.txt) 18 |
Copyright (c) Chen, Yi-Cyuan 2017 19 | 20 | ### [genanki](https://github.com/kerrickstaley/genanki) 21 | [The MIT License](https://github.com/kerrickstaley/genanki/blob/master/LICENSE.txt) 22 |
Copyright (c) Kerrick Staley 2021 23 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # genanki-js 2 | A JavaScript implementation for generating Anki decks in browser. This is fork of [mkanki](https://github.com/nornagon/mkanki). 3 | 4 | # QuickStart 5 | Download [genanki](https://github.com/krmanik/genanki-js/releases) zip file from release pages. 6 | 7 | The zip file contains two folder 8 | 9 | - `dist` : This folder contains `genanki.js` file. If project already configured with [sql.js](https://github.com/sql-js/sql.js), [FileSaver.js](https://github.com/eligrey/FileSaver.js) and [JSZip](https://github.com/Stuk/jszip) then just add `genanki.js` file to project. 10 | 11 | - `sample` : This folder contains latest [sql.js](https://github.com/sql-js/sql.js), [FileSaver.js](https://github.com/eligrey/FileSaver.js), [JSZip](https://github.com/Stuk/jszip) and [genanki.js](https://github.com/krmanik/genanki-js). It is ready to use folder. 12 | 13 | Alternatively, `genanki.js` can also be loaded from CDN. 14 | ```html 15 | 16 | ``` 17 | 18 | # Set Up a new project from scratch 19 | #### 1. Download `genanki.js` from [dist](https://github.com/krmanik/genanki-js/tree/main/dist) folder and add to the project 20 | 21 | ```html 22 | 23 | 24 | ``` 25 | 26 | #### 2. Add [sql.js](https://github.com/sql-js/sql.js), [FileSaver.js](https://github.com/eligrey/FileSaver.js) and [JSZip](https://github.com/Stuk/jszip) to the project 27 | 28 | >*Note: [mkanki](https://github.com/nornagon/mkanki) uses `better-sql`, `fs` and `archiver`, that make it difficult to be used in browser* 29 | 30 | ```html 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | 41 | #### 3. Create a `SQL` global variable (may be added to index.js) 42 | 43 | >Note: The `SQL` variable is used in [package.js#L20](https://github.com/krmanik/genanki-js/blob/main/genanki/package.js#L20). 44 | 45 | ```js 46 | // The `initSqlJs` function is globally provided by all of the main dist files if loaded in the browser. 47 | // We must specify this locateFile function if we are loading a wasm file from anywhere other than the current html page's folder. 48 | config = { 49 | locateFile: filename => `js/sql/sql-wasm.wasm` 50 | } 51 | 52 | var SQL; 53 | initSqlJs(config).then(function (sql) { 54 | //Create the database 55 | SQL = sql; 56 | }); 57 | ``` 58 | 59 | #### 4. After finishing above, the project structure should be like this 60 | 61 | ``` 62 | . 63 | └── sample 64 | ├── js 65 | │ ├── anki 66 | │ │ └── genanki.js 67 | │ ├── filesaver 68 | │ │ ├── FileSaver.min.js 69 | │ │ └── FileSaver.min.js.map 70 | │ ├── sql 71 | │ │ ├── sql.js 72 | │ │ └── sql-wasm.wasm 73 | │ ├── jszip.min.js 74 | │ └── index.js 75 | └── index.html 76 | ``` 77 | 78 | #### 5. Now use following `Examples` to generate and export decks. 79 | 80 | View more examples here [Examples](https://krmanik.github.io/genanki-js/demo/index.html) 81 | 82 | ##### Examples 83 | 84 | ```js 85 | var m = new Model({ 86 | name: "Basic (and reversed card)", 87 | id: "1543634829843", 88 | flds: [ 89 | { name: "Front" }, 90 | { name: "Back" } 91 | ], 92 | req: [ 93 | [ 0, "all", [ 0 ] ], 94 | [ 1, "all", [ 1 ] ] 95 | ], 96 | tmpls: [ 97 | { 98 | name: "Card 1", 99 | qfmt: "{{Front}}", 100 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 101 | }, 102 | { 103 | name: "Card 2", 104 | qfmt: "{{Back}}", 105 | afmt: "{{FrontSide}}\n\n
\n\n{{Front}}", 106 | } 107 | ], 108 | }) 109 | 110 | var d = new Deck(1276438724672, "Test Deck") 111 | 112 | d.addNote(m.note(['this is front', 'this is back'])) 113 | 114 | var p = new Package() 115 | p.addDeck(d) 116 | 117 | p.writeToFile('deck.apkg') 118 | 119 | ``` 120 | 121 | # License 122 | ## [genanki-js](https://github.com/krmanik/genanki-js) 123 | [GNU Affero General Public License v3](https://opensource.org/licenses/AGPL-3.0) 124 |
Copyright (c) 2021 Mani 125 | 126 | ## Other Third Party Licenses 127 | [License.md](License.md) -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | * # Demo 2 | * # Fork -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Quick start](README.md) 2 | - Demo 3 | - [Documentation](documentation.md) 4 | - [Model](documentation.md#Model) 5 | - [Deck](documentation.md#Deck) 6 | - [Package](documentation.md#Package) 7 | - [Examples](examples.md) 8 | - [Basic and reversed card](examples.md#basic-and-reversed-card) 9 | - [Two notes](examples.md#two-notes) 10 | - [Ten notes in loop](examples.md#ten-notes-in-loop) 11 | - [Two Chinese notes](examples.md#two-chinese-notes) 12 | - [Add image file](examples.md#add-image-file) 13 | - [Add tags to notes](examples?id=add-tags-to-notes) 14 | - [License](License.md) -------------------------------------------------------------------------------- /docs/demo/css/demo.css: -------------------------------------------------------------------------------- 1 | #snackbar { 2 | visibility: hidden; 3 | background-color: #333; 4 | color: #fff; 5 | text-align: center; 6 | border-radius: 2px; 7 | padding: 10px; 8 | position: fixed; 9 | z-index: 1000; 10 | bottom: 60px; 11 | left: 0; 12 | right: 0; 13 | margin: 10px; 14 | } 15 | #snackbar.show { 16 | visibility: visible; 17 | -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; 18 | animation: fadein 0.5s, fadeout 0.5s 2.5s; 19 | } 20 | @-webkit-keyframes fadein { 21 | from { 22 | bottom: 0; 23 | opacity: 0; 24 | } 25 | to { 26 | bottom: 60px; 27 | opacity: 1; 28 | } 29 | } 30 | @keyframes fadein { 31 | from { 32 | bottom: 0; 33 | opacity: 0; 34 | } 35 | to { 36 | bottom: 60px; 37 | opacity: 1; 38 | } 39 | } 40 | @-webkit-keyframes fadeout { 41 | from { 42 | bottom: 60px; 43 | opacity: 1; 44 | } 45 | to { 46 | bottom: 0; 47 | opacity: 0; 48 | } 49 | } 50 | @keyframes fadeout { 51 | from { 52 | bottom: 60px; 53 | opacity: 1; 54 | } 55 | to { 56 | bottom: 0; 57 | opacity: 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/demo/csv_to_apkg.js: -------------------------------------------------------------------------------- 1 | var content = null; 2 | var firstLine = null; 3 | var fieldsCount = null; 4 | var file = null; 5 | var reader = null; 6 | var delimiter = null; 7 | 8 | function inputFile() { 9 | file = document.getElementById("file").files[0]; 10 | reader = new FileReader(); 11 | } 12 | 13 | function importFile() { 14 | try { 15 | 16 | delimiter = document.getElementById('fieldSeparatedBy').value; 17 | 18 | if (delimiter == "tab") { 19 | delimiter = "\t"; 20 | } else if (delimiter == "comma") { 21 | delimiter = ","; 22 | } 23 | 24 | reader.readAsText(file); 25 | reader.onload = evt => { 26 | content = evt.target.result; 27 | //console.log(content); 28 | 29 | firstLine = content.split('\n').shift(); 30 | console.log(firstLine); 31 | 32 | 33 | 34 | document.getElementById("firstEntry").innerHTML = `
35 |
36 |

First Line of the file

` 37 | + firstLine + 38 | `
`; 39 | 40 | fieldsCount = firstLine.split(delimiter).length; 41 | // console.log(fieldsCount); 42 | 43 | var fields = ""; 44 | var j = 0; 45 | 46 | 47 | 48 | for (i = 0; i < fieldsCount; i++) { 49 | j = i + 1; 50 | fields += `
51 | 52 | 53 |
54 |
55 | 56 | 59 |
60 | 61 |
62 | 63 | 66 |
67 |
68 |
69 |
70 | ` 71 | } 72 | 73 | document.getElementById('fields').innerHTML = fields; 74 | 75 | document.getElementById("fields-card").style.display = "block"; 76 | document.getElementById("fields-msg").innerHTML = "
Enter fields name
"; 77 | } 78 | 79 | } catch (error) { 80 | console.error(error); 81 | document.getElementById("fields-card").style.display = "none"; 82 | showSnackbar("Failed to import file. Check file or try again"); 83 | } 84 | 85 | } 86 | 87 | 88 | function exportDeck() { 89 | var field; 90 | var fields = []; 91 | 92 | var front = ""; 93 | var back = ""; 94 | 95 | for (i = 0; i < fieldsCount; i++) { 96 | checkFront = document.getElementById("checkFront" + i).checked; 97 | checkBack = document.getElementById("checkBack" + i).checked; 98 | 99 | field = "field" + i 100 | fl = document.getElementById(field).value; 101 | 102 | if (fl == "") { 103 | showSnackbar("fields are empty"); 104 | return; 105 | } 106 | 107 | fields.push({ name: fl }); 108 | 109 | if (!checkFront && !checkBack) { 110 | showSnackbar("select fields as front, back or both"); 111 | return; 112 | } 113 | 114 | if (checkFront) { 115 | front += "{{" + fl + "}}\n
\n" 116 | } 117 | 118 | if (checkBack) { 119 | back += "{{" + fl + "}}\n
\n" 120 | } 121 | } 122 | 123 | const m = new Model({ 124 | name: "Basic", 125 | id: "3765467234656", 126 | flds: fields, 127 | req: [ 128 | [0, "all", [0]], 129 | ], 130 | tmpls: [ 131 | { 132 | name: "Card 1", 133 | qfmt: front, 134 | afmt: back, 135 | } 136 | ], 137 | }) 138 | 139 | deckName = document.getElementById("deckName").value; 140 | if (deckName == "" || deckName == undefined) { 141 | showSnackbar("Enter a valid deck name") 142 | return; 143 | } 144 | 145 | try { 146 | document.getElementById("exportMsg").innerHTML = "Wait... Decks are exporting"; 147 | 148 | const d = new Deck(1347617346765, deckName) 149 | 150 | var lines = content.split("\n"); 151 | var notAddedCount = 0; 152 | var addedCount = 0; 153 | var notAddedTxt = ""; 154 | for (l of lines) { 155 | // console.log(l); 156 | var noteData = l.split("\t"); 157 | 158 | if (noteData.length == fieldsCount) { 159 | addedCount++; 160 | d.addNote(m.note(noteData)) 161 | } else { 162 | notAddedCount++; 163 | notAddedTxt += l + "\n"; 164 | showSnackbar(notAddedCount + " card not added. View notAdded.txt files"); 165 | } 166 | 167 | document.getElementById("exportMsg").innerHTML = addedCount + " card added and exporetd to the decks"; 168 | } 169 | 170 | // not added text 171 | if (notAddedTxt.trim() != "") { 172 | var filename = "notAdded.txt"; 173 | var blob = new Blob([notAddedTxt], { 174 | type: "text/plain;charset=utf-8" 175 | }); 176 | setTimeout(function(){ saveAs(blob, filename); }, 500); 177 | } 178 | 179 | const p = new Package() 180 | p.addDeck(d) 181 | 182 | p.writeToFile('Export-Deck.apkg') 183 | } catch (err) { 184 | console.error(err) 185 | document.getElementById("exportMsg").innerHTML = err; 186 | showSnackbar("Error exporting deck, check tsv file's fields") 187 | } 188 | 189 | } -------------------------------------------------------------------------------- /docs/demo/demo.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | changePage("csv_apkg"); 3 | } 4 | 5 | function changePage(page) { 6 | document.getElementById("exportMsg").innerHTML = ""; 7 | switch (page) { 8 | case 'example': 9 | document.getElementById("run_example").style.display = "block"; 10 | document.getElementById("csv_to_apkg").style.display = "none"; 11 | break; 12 | case 'csv_apkg': 13 | document.getElementById("run_example").style.display = "none"; 14 | document.getElementById("csv_to_apkg").style.display = "block"; 15 | break; 16 | } 17 | } 18 | 19 | 20 | var isInit = false; 21 | var isShownDevTools = false; 22 | function turnOnDevTools() { 23 | 24 | if (!isInit) { 25 | isInit = true; 26 | var script = document.createElement('script'); 27 | script.src = "js/eruda.js"; 28 | document.body.appendChild(script); 29 | script.onload = function () { 30 | eruda.init(); 31 | eruda.show(); 32 | } 33 | } 34 | 35 | if (isShownDevTools) { 36 | isShownDevTools = false; 37 | eruda.hide(); 38 | 39 | document.getElementById("run_example").style.paddingBottom = "0%"; 40 | document.getElementById("csv_to_apkg").style.paddingBottom = "0%"; 41 | } else { 42 | isShownDevTools = true; 43 | eruda.show(); 44 | 45 | document.getElementById("run_example").style.paddingBottom = "50%"; 46 | document.getElementById("csv_to_apkg").style.paddingBottom = "50%"; 47 | } 48 | } 49 | 50 | // https://stackoverflow.com/questions/13243563/execute-javascript-from-textarea 51 | function runCode() { 52 | var oldScript = document.getElementById('scriptContainer'); 53 | var newScript; 54 | 55 | if (oldScript) { 56 | oldScript.parentNode.removeChild(oldScript); 57 | } 58 | 59 | newScript = document.createElement('script'); 60 | newScript.id = 'scriptContainer'; 61 | 62 | // get code text from code mirror 63 | newScript.text = editor.getValue(); 64 | document.body.appendChild(newScript); 65 | } 66 | 67 | 68 | function changeCode(code) { 69 | var codeTitle = document.getElementById("codeTitle"); 70 | 71 | switch (code) { 72 | case "code1": 73 | editor.setValue(code1); 74 | codeTitle.innerHTML = "Basic and reversed card"; 75 | break; 76 | case "code2": 77 | editor.setValue(code2); 78 | codeTitle.innerHTML = "Two notes"; 79 | break; 80 | case "code3": 81 | editor.setValue(code3); 82 | codeTitle.innerHTML = "Ten notes in loop"; 83 | break; 84 | case "code4": 85 | editor.setValue(code4); 86 | codeTitle.innerHTML = "Two Chinese notes"; 87 | break; 88 | case "code5": 89 | editor.setValue(code5); 90 | codeTitle.innerHTML = "Add image file"; 91 | break; 92 | case "code6": 93 | editor.setValue(code6); 94 | codeTitle.innerHTML = "Test add tags to notes"; 95 | break; 96 | } 97 | } 98 | 99 | /** 100 | * Basic and reversed card 101 | */ 102 | var code1 = String.raw`var m = new Model({ 103 | name: "Basic (and reversed card)", 104 | id: "1543634829843", 105 | flds: [ 106 | { name: "Front" }, 107 | { name: "Back" } 108 | ], 109 | req: [ 110 | [ 0, "all", [ 0 ] ], 111 | [ 1, "all", [ 1 ] ] 112 | ], 113 | tmpls: [ 114 | { 115 | name: "Card 1", 116 | qfmt: "{{Front}}", 117 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 118 | }, 119 | { 120 | name: "Card 2", 121 | qfmt: "{{Back}}", 122 | afmt: "{{FrontSide}}\n\n
\n\n{{Front}}", 123 | } 124 | ], 125 | }) 126 | 127 | var d = new Deck(1276438724672, "Test Deck") 128 | 129 | d.addNote(m.note(['this is front', 'this is back'])) 130 | 131 | var p = new Package() 132 | p.addDeck(d) 133 | 134 | p.writeToFile('deck.apkg')`; 135 | 136 | /** 137 | * Two notes 138 | */ 139 | var code2 = String.raw`var m = new Model({ 140 | name: "Basic", 141 | id: "1542906796044", 142 | flds: [ 143 | { name: "Front" }, 144 | { name: "Back" } 145 | ], 146 | req: [ 147 | [0, "all", [0]], 148 | ], 149 | tmpls: [ 150 | { 151 | name: "Card 1", 152 | qfmt: "{{Front}}", 153 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 154 | } 155 | ], 156 | }) 157 | 158 | var d = new Deck(1347617346765, "Two notes") 159 | 160 | d.addNote(m.note(['hello', 'world'])) 161 | d.addNote(m.note(['this is test', 'for anki'])) 162 | 163 | var p = new Package() 164 | p.addDeck(d) 165 | p.writeToFile('deck.apkg')`; 166 | 167 | /** 168 | * Ten notes in loop 169 | */ 170 | var code3 = String.raw`var m = new Model({ 171 | name: "Basic", 172 | id: "1542906796044", 173 | flds: [ 174 | { name: "Front" }, 175 | { name: "Back" } 176 | ], 177 | req: [ 178 | [0, "all", [0]], 179 | ], 180 | tmpls: [ 181 | { 182 | name: "Card 1", 183 | qfmt: "{{Front}}", 184 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 185 | } 186 | ], 187 | }) 188 | 189 | var d = new Deck(1347617346765, "Ten notes in loop") 190 | 191 | for (i = 0; i < 10; i++) { 192 | d.addNote(m.note(['front note ' + i, 'back note ' + i])) 193 | } 194 | 195 | var p = new Package() 196 | p.addDeck(d) 197 | p.writeToFile('deck.apkg')`; 198 | 199 | /** 200 | * Two Chinese notes 201 | */ 202 | var code4 = String.raw`var m = new Model({ 203 | name: "Basic", 204 | id: "1542906796045", 205 | flds: [ 206 | { name: "Simplified" }, 207 | { name: "Traditional" }, 208 | { name: "Pinyin" }, 209 | { name: "Meaning" }, 210 | ], 211 | req: [ 212 | [0, "all", [0]], 213 | ], 214 | tmpls: [ 215 | { 216 | name: "Card 1", 217 | qfmt: "{{Simplified}}\n\n{{Pinyin}}", 218 | afmt: "{{FrontSide}}\n\n
\n\n{{Traditional}}\n\n{{Meaning}}", 219 | } 220 | ], 221 | }) 222 | 223 | var d = new Deck(1347617346765, "Chinese New Words") 224 | 225 | d.addNote(m.note(["如", "如", "rú", "according to, in accordance with, such as, as if, like, for example"])) 226 | d.addNote(m.note(["比如", "譬如", "pìrú", "for example, such as"])) 227 | 228 | var p = new Package() 229 | p.addDeck(d) 230 | p.writeToFile('chinese-deck.apkg')`; 231 | 232 | /** 233 | * Add image file 234 | */ 235 | var code5 = String.raw`var m = new Model({ 236 | name: "Basic Test", 237 | id: "3457826374725", 238 | flds: [ 239 | { name: "Front" }, 240 | { name: "Back" } 241 | ], 242 | req: [ 243 | [0, "all", [0]], 244 | ], 245 | tmpls: [ 246 | { 247 | name: "Card 1", 248 | qfmt: "{{Front}}", 249 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 250 | } 251 | ], 252 | }) 253 | 254 | var d = new Deck(1347617346765, "Add image file") 255 | 256 | var imageFile = "favicon.ico"; 257 | 258 | d.addNote(m.note(['This is front and back side contains image.', ''])) 259 | 260 | var p = new Package() 261 | p.addDeck(d) 262 | 263 | fetch('favicon.ico').then(response => { 264 | if (!response.ok) { 265 | return null; 266 | } 267 | 268 | p.addMedia(response.blob(), imageFile); 269 | p.writeToFile('deck.apkg') 270 | });`; 271 | 272 | 273 | /** 274 | * Test add tags to notes 275 | */ 276 | var code6 = String.raw`var m = new Model({ 277 | name: "Basic (and reversed card) with tags", 278 | id: "1543634829847", 279 | flds: [ 280 | { name: "Front" }, 281 | { name: "Back" } 282 | ], 283 | req: [ 284 | [ 0, "all", [ 0 ] ], 285 | [ 1, "all", [ 1 ] ] 286 | ], 287 | tmpls: [ 288 | { 289 | name: "Card 1", 290 | qfmt: "{{Front}}", 291 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 292 | }, 293 | { 294 | name: "Card 2", 295 | qfmt: "{{Back}}", 296 | afmt: "{{FrontSide}}\n\n
\n\n{{Front}}", 297 | } 298 | ], 299 | }) 300 | 301 | var d = new Deck(1276438724678, "Test Deck wit tags") 302 | 303 | d.addNote(m.note(['this is front', 'this is back'], ['test_tag1', 'test_tag2'])) 304 | 305 | var p = new Package() 306 | p.addDeck(d) 307 | 308 | p.writeToFile('deck.apkg')`; 309 | 310 | 311 | function showSnackbar(msg) { 312 | var x = document.getElementById("snackbar"); 313 | 314 | x.innerHTML = msg; 315 | x.className = "show"; 316 | 317 | setTimeout(function () { x.className = x.className.replace("show", ""); }, 3000); 318 | } -------------------------------------------------------------------------------- /docs/demo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krmanik/genanki-js/7a88bc5d3a22ae375309080daffc36f4e11e51cd/docs/demo/favicon.ico -------------------------------------------------------------------------------- /docs/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | genanki-js demo 7 | 8 | 10 | 11 | 13 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 68 | 69 |
70 | 71 |
72 | 76 | 84 |
85 | 86 | 87 |
88 | 89 |
90 |

Basic and reversed card

91 |
92 | 93 |
94 | 95 |
96 | 131 | 132 |
133 | 134 |
135 | 136 |
137 | 138 |
139 | 140 |
141 | 142 |
143 | 144 | 222 | 223 |
224 | 225 | 238 | 239 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /docs/demo/js/anki/genanki.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [mkanki]{@link https://github.com/nornagon/mkanki} 3 | * @copyright Copyright (c) 2018 Jeremy Apthorp 4 | * @license AGPL-3.0 License 5 | * 6 | * 7 | * [genanki]{@link https://github.com/kerrickstaley/genanki} 8 | * @copyright Copyright (c) Kerrick Staley 2021 9 | * @license The MIT License 10 | * 11 | * 12 | * [genanki-js]{@link https://github.com/krmanik/genanki-js} 13 | * @copyright Copyright (c) 2021 Mani 14 | * @license AGPL-3.0 License 15 | */ 16 | 17 | BASE91_TABLE = [ 18 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 19 | 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 20 | 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', 21 | '5', '6', '7', '8', '9', '!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '-', '.', '/', ':', 22 | ';', '<', '=', '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~'] 23 | 24 | function ankiHash(fields) { 25 | const str = fields.join('__') 26 | const h = sha256.create(); 27 | h.update(str) 28 | const hex = h.digest() 29 | 30 | let hash_int = 0n 31 | for (let i = 0; i < 8; i++) { 32 | hash_int *= 256n 33 | hash_int += BigInt(hex[i]) 34 | } 35 | 36 | // convert to the weird base91 format that Anki uses 37 | let rv_reversed = [] 38 | while (hash_int > 0) { 39 | rv_reversed.push(BASE91_TABLE[hash_int % 91n]) 40 | hash_int = (hash_int / 91n) 41 | } 42 | 43 | return rv_reversed.reverse().join('') 44 | } 45 | 46 | const MODEL_STD = 0 47 | const MODEL_CLOZE = 1 48 | 49 | class Model { 50 | constructor(props) { 51 | this.props = { 52 | ...defaultModel, 53 | ...props, 54 | flds: props.flds.map((f, i) => ({ ...defaultField, ord: i, ...f })), 55 | tmpls: props.tmpls.map((t, i) => ({ ...defaultTemplate, ord: i, name: `Card ${i + 1}`, ...t })), 56 | mod: new Date().getTime() 57 | } 58 | this.fieldNameToOrd = {} 59 | this.props.flds.forEach(f => { this.fieldNameToOrd[f.name] = f.ord }) 60 | } 61 | 62 | note(fields, tags, guid = null) { 63 | if (Array.isArray(fields)) { 64 | if (fields.length !== this.props.flds.length) { 65 | throw new Error(`Expected ${this.props.flds.length} fields for model '${this.props.name}' but got ${fields.length}`) 66 | } 67 | return new Note(this, fields, tags, guid) 68 | } else { 69 | const field_names = Object.keys(fields) 70 | const fields_list = [] 71 | field_names.forEach(field_name => { 72 | const ord = this.fieldNameToOrd[field_name] 73 | if (ord == null) throw new Error(`Field '${field_name}' does not exist in the model`) 74 | fields_list[ord] = fields[field_name] 75 | }) 76 | return new Note(this, fields_list, tags, guid) 77 | } 78 | } 79 | } 80 | 81 | class ClozeModel extends Model { 82 | constructor(props) { 83 | super({ 84 | type: MODEL_CLOZE, 85 | css: ` 86 | .card { 87 | font-family: arial; 88 | font-size: 20px; 89 | text-align: center; 90 | color: black; 91 | background-color: white; 92 | } 93 | 94 | .cloze { 95 | font-weight: bold; 96 | color: blue; 97 | } 98 | `, 99 | tmpls: [{ name: "Cloze", ...props.tmpl }], 100 | ...props 101 | }) 102 | } 103 | } 104 | 105 | const defaultModel = { 106 | sortf: 0, // sort field 107 | did: 1, // deck id 108 | latexPre: `\\documentclass[12pt]{article} 109 | \\special{papersize=3in,5in} 110 | \\usepackage[utf8]{inputenc} 111 | \\usepackage{amssymb,amsmath} 112 | \\pagestyle{empty} 113 | \\setlength{\\parindent}{0in} 114 | \\begin{document}`, 115 | latexPost: "\\end{document}", 116 | mod: 0, // modification time 117 | usn: 0, // unsure, something to do with sync? 118 | vers: [], // seems to be unused 119 | type: MODEL_STD, 120 | css: `.card { 121 | font-family: arial; 122 | font-size: 20px; 123 | text-align: center; 124 | color: black; 125 | background-color: white; 126 | }`, 127 | /* also: 128 | name: string, 129 | flds: [Field], 130 | tmpls: [Template], 131 | tags: [??], 132 | id: string 133 | */ 134 | tags: [], 135 | } 136 | 137 | const defaultField = { 138 | name: "", 139 | ord: null, 140 | sticky: false, 141 | rtl: false, 142 | font: "Arial", 143 | size: 20, 144 | media: [], 145 | } 146 | 147 | const defaultTemplate = { 148 | name: "", 149 | ord: null, 150 | qfmt: "", 151 | afmt: "", 152 | did: null, 153 | bqfmt: "", 154 | bafmt: "", 155 | } 156 | 157 | // whether new cards should be mixed with reviews, or shown first or last 158 | const NEW_CARDS_DISTRIBUTE = 0 159 | const NEW_CARDS_LAST = 1 160 | const NEW_CARDS_FIRST = 2 161 | 162 | const defaultConf = { 163 | // review options 164 | 'activeDecks': [1], 165 | 'curDeck': 1, 166 | 'newSpread': NEW_CARDS_DISTRIBUTE, 167 | 'collapseTime': 1200, 168 | 'timeLim': 0, 169 | 'estTimes': true, 170 | 'dueCounts': true, 171 | // other config 172 | 'curModel': null, 173 | 'nextPos': 1, 174 | 'sortType': "noteFld", 175 | 'sortBackwards': false, 176 | 'addToCur': true, // add new to currently selected deck? 177 | 'dayLearnFirst': false, 178 | } 179 | 180 | 181 | // new card insertion order 182 | const NEW_CARDS_RANDOM = 0 183 | const NEW_CARDS_DUE = 1 184 | 185 | const STARTING_FACTOR = 2500 186 | 187 | const defaultDeckConf = { 188 | 'name': "Default", 189 | 'new': { 190 | 'delays': [1, 10], 191 | 'ints': [1, 4, 7], // 7 is not currently used 192 | 'initialFactor': STARTING_FACTOR, 193 | 'separate': true, 194 | 'order': NEW_CARDS_DUE, 195 | 'perDay': 20, 196 | // may not be set on old decks 197 | 'bury': false, 198 | }, 199 | 'lapse': { 200 | 'delays': [10], 201 | 'mult': 0, 202 | 'minInt': 1, 203 | 'leechFails': 8, 204 | // type 0=suspend, 1=tagonly 205 | 'leechAction': 0, 206 | }, 207 | 'rev': { 208 | 'perDay': 200, 209 | 'ease4': 1.3, 210 | 'fuzz': 0.05, 211 | 'minSpace': 1, // not currently used 212 | 'ivlFct': 1, 213 | 'maxIvl': 36500, 214 | // may not be set on old decks 215 | 'bury': false, 216 | 'hardFactor': 1.2, 217 | }, 218 | 'maxTaken': 60, 219 | 'timer': 0, 220 | 'autoplay': true, 221 | 'replayq': true, 222 | 'mod': 0, 223 | 'usn': 0, 224 | } 225 | 226 | const defaultDeck = { 227 | newToday: [0, 0], // currentDay, count 228 | revToday: [0, 0], 229 | lrnToday: [0, 0], 230 | timeToday: [0, 0], // time in ms 231 | conf: 1, 232 | usn: 0, 233 | desc: "", 234 | dyn: 0, // anki uses int/bool interchangably here 235 | collapsed: false, 236 | // added in beta11 237 | extendNew: 10, 238 | extendRev: 50, 239 | } 240 | 241 | class Deck { 242 | constructor(id, name, desc="") { 243 | this.id = id 244 | this.name = name 245 | this.desc = desc 246 | this.notes = [] 247 | } 248 | 249 | addNote(note) { 250 | this.notes.push(note) 251 | } 252 | } 253 | 254 | class Note { 255 | constructor(model, fields, tags = null, guid = null) { 256 | this.model = model 257 | this.fields = fields 258 | this.tags = tags 259 | this._guid = guid 260 | } 261 | 262 | get guid() { 263 | return this._guid ? this._guid : ankiHash(this.fields); 264 | } 265 | 266 | get cards() { 267 | if (this.model.props.type === MODEL_STD) { 268 | const isEmpty = f => { 269 | return !f || f.toString().trim().length === 0 270 | } 271 | const rv = [] 272 | for (const [card_ord, any_or_all, required_field_ords] of this.model.props.req) { 273 | const op = any_or_all === "any" ? "some" : "every" 274 | if (required_field_ords[op](f => !isEmpty(this.fields[f]))) { 275 | rv.push(card_ord) 276 | } 277 | } 278 | return rv 279 | } else { 280 | // the below logic is copied from anki's ModelManager._availClozeOrds 281 | const ords = new Set() 282 | const matches = [] 283 | const curliesRe = /{{[^}]*?cloze:(?:[^}]?:)*(.+?)}}/g 284 | const percentRe = /<%cloze:(.+?)%>/g 285 | const { qfmt } = this.model.props.tmpls[0] // cloze models only have 1 template 286 | let m; 287 | while (m = curliesRe.exec(qfmt)) 288 | matches.push(m[1]) 289 | while (m = percentRe.exec(qfmt)) 290 | matches.push(m[1]) 291 | const map = {} 292 | this.model.props.flds.forEach((fld, i) => { 293 | map[fld.name] = [i, fld] 294 | }) 295 | for (const fname of matches) { 296 | if (!(fname in map)) continue 297 | const ord = map[fname][0] 298 | const re = /{{c(\d+)::.+?}}/gs 299 | while (m = re.exec(this.fields[ord])) { 300 | const i = parseInt(m[1]) 301 | if (i > 0) 302 | ords.add(i - 1) 303 | } 304 | } 305 | if (ords.size === 0) { 306 | // empty clozes use first ord 307 | return [0] 308 | } 309 | return Array.from(ords) 310 | } 311 | } 312 | } 313 | 314 | class Package { 315 | constructor() { 316 | this.decks = [] 317 | this.media = [] 318 | } 319 | 320 | addDeck(deck) { 321 | this.decks.push(deck) 322 | } 323 | 324 | addMedia(data, name) { 325 | this.media.push({ name, data }) 326 | } 327 | 328 | addMediaFile(filename, name = null) { 329 | this.media.push({ name: name || filename, filename }) 330 | } 331 | 332 | writeToFile(filename) { 333 | var db = new SQL.Database(); 334 | db.run(APKG_SCHEMA); 335 | 336 | this.write(db) 337 | 338 | var zip = new JSZip(); 339 | 340 | const data = db.export(); 341 | const buffer = new Uint8Array(data).buffer; 342 | 343 | zip.file("collection.anki2", buffer); 344 | 345 | const media_info = {} 346 | 347 | this.media.forEach((m, i) => { 348 | if (m.filename != null) { 349 | zip.file(i.toString(), m.filename) 350 | } else { 351 | zip.file(i.toString(), m.data) 352 | } 353 | 354 | media_info[i] = m.name 355 | }) 356 | 357 | zip.file('media', JSON.stringify(media_info)) 358 | 359 | zip.generateAsync({ type: "blob", mimeType: "application/apkg" }).then(function (content) { 360 | // see FileSaver.js 361 | saveAs(content, filename); 362 | }); 363 | } 364 | 365 | 366 | write(db) { 367 | const now = new Date 368 | const models = {} 369 | const decks = {} 370 | 371 | // AnkiDroid failed to import subdeck, So add a Default deck 372 | decks["1"] = {...defaultDeck, id: 1, name: "Default"} 373 | 374 | this.decks.forEach(d => { 375 | d.notes.forEach(n => models[n.model.props.id] = n.model.props) 376 | decks[d.id] = { 377 | ...defaultDeck, 378 | id: d.id, 379 | name: d.name, 380 | desc: d.desc, 381 | } 382 | }) 383 | 384 | const col = [ 385 | null, // id 386 | (+now / 1000) | 0, // crt 387 | +now, // mod 388 | +now, // scm 389 | 11, // ver 390 | 0, // dty 391 | 0, // usn 392 | 0, // ls 393 | JSON.stringify(defaultConf), // conf 394 | JSON.stringify(models), // models 395 | JSON.stringify(decks), // decks 396 | JSON.stringify({ 1: { id: 1, ...defaultDeckConf } }), // dconf 397 | JSON.stringify({}), // tags 398 | ] 399 | 400 | db.prepare(`INSERT INTO col 401 | (id, crt, mod, scm, ver, dty, usn, ls, conf, models, decks, dconf, tags) 402 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(col) 403 | 404 | const insert_notes = db.prepare(`INSERT INTO notes (id, guid, mid, mod, usn, tags, flds, sfld, csum, flags, data) 405 | VALUES (null, ?, ?, ?, ?, ?, ?, ?, 0, 0, '')`) 406 | 407 | const insert_cards = db.prepare(`INSERT INTO cards (id, nid, did, ord, mod, usn, type, queue, due, ivl, factor, reps, lapses, left, odue, odid, flags, data) 408 | VALUES (null, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, 0, 0, 0, 0, 0, 0, '')`) 409 | 410 | for (const deck of this.decks) { 411 | for (const note of deck.notes) { 412 | var tags = note.tags == null ? '' : note.tags.join(' ') 413 | insert_notes.run( 414 | [ 415 | note.guid, // guid 416 | note.model.props.id, // mid 417 | (+now / 1000) | 0, // mod 418 | -1, // usn 419 | tags, // tags 420 | note.fields.join('\x1f'), // flds 421 | 0, // sfld 422 | ]) 423 | 424 | var rowID = db.exec("select last_insert_rowid();") 425 | var note_id = rowID[0]['values'][0][0]; 426 | 427 | for (const card_ord of note.cards) { 428 | insert_cards.run( 429 | [ 430 | note_id, // nid 431 | deck.id, // did 432 | card_ord, // ord 433 | (+now / 1000) | 0, // mod 434 | -1, // usn 435 | 0, // type 0=new, 1=learning, 2=due 436 | 0, // queue -1 for suspended 437 | ]) 438 | } 439 | } 440 | } 441 | } 442 | } 443 | 444 | /** 445 | * [js-sha256]{@link https://github.com/emn178/js-sha256} 446 | * 447 | * @version 0.9.0 448 | * @author Chen, Yi-Cyuan [emn178@gmail.com] 449 | * @copyright Chen, Yi-Cyuan 2014-2017 450 | * @license MIT 451 | */ 452 | /*jslint bitwise: true */ 453 | (function () { 454 | 'use strict'; 455 | 456 | var ERROR = 'input is invalid type'; 457 | var WINDOW = typeof window === 'object'; 458 | var root = WINDOW ? window : {}; 459 | if (root.JS_SHA256_NO_WINDOW) { 460 | WINDOW = false; 461 | } 462 | var WEB_WORKER = !WINDOW && typeof self === 'object'; 463 | var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; 464 | if (NODE_JS) { 465 | root = global; 466 | } else if (WEB_WORKER) { 467 | root = self; 468 | } 469 | var COMMON_JS = !root.JS_SHA256_NO_COMMON_JS && typeof module === 'object' && module.exports; 470 | var AMD = typeof define === 'function' && define.amd; 471 | var ARRAY_BUFFER = !root.JS_SHA256_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; 472 | var HEX_CHARS = '0123456789abcdef'.split(''); 473 | var EXTRA = [-2147483648, 8388608, 32768, 128]; 474 | var SHIFT = [24, 16, 8, 0]; 475 | var K = [ 476 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 477 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 478 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 479 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 480 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 481 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 482 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 483 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 484 | ]; 485 | var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer']; 486 | 487 | var blocks = []; 488 | 489 | if (root.JS_SHA256_NO_NODE_JS || !Array.isArray) { 490 | Array.isArray = function (obj) { 491 | return Object.prototype.toString.call(obj) === '[object Array]'; 492 | }; 493 | } 494 | 495 | if (ARRAY_BUFFER && (root.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) { 496 | ArrayBuffer.isView = function (obj) { 497 | return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; 498 | }; 499 | } 500 | 501 | var createOutputMethod = function (outputType, is224) { 502 | return function (message) { 503 | return new Sha256(is224, true).update(message)[outputType](); 504 | }; 505 | }; 506 | 507 | var createMethod = function (is224) { 508 | var method = createOutputMethod('hex', is224); 509 | if (NODE_JS) { 510 | method = nodeWrap(method, is224); 511 | } 512 | method.create = function () { 513 | return new Sha256(is224); 514 | }; 515 | method.update = function (message) { 516 | return method.create().update(message); 517 | }; 518 | for (var i = 0; i < OUTPUT_TYPES.length; ++i) { 519 | var type = OUTPUT_TYPES[i]; 520 | method[type] = createOutputMethod(type, is224); 521 | } 522 | return method; 523 | }; 524 | 525 | var nodeWrap = function (method, is224) { 526 | var crypto = eval("require('crypto')"); 527 | var Buffer = eval("require('buffer').Buffer"); 528 | var algorithm = is224 ? 'sha224' : 'sha256'; 529 | var nodeMethod = function (message) { 530 | if (typeof message === 'string') { 531 | return crypto.createHash(algorithm).update(message, 'utf8').digest('hex'); 532 | } else { 533 | if (message === null || message === undefined) { 534 | throw new Error(ERROR); 535 | } else if (message.constructor === ArrayBuffer) { 536 | message = new Uint8Array(message); 537 | } 538 | } 539 | if (Array.isArray(message) || ArrayBuffer.isView(message) || 540 | message.constructor === Buffer) { 541 | return crypto.createHash(algorithm).update(new Buffer(message)).digest('hex'); 542 | } else { 543 | return method(message); 544 | } 545 | }; 546 | return nodeMethod; 547 | }; 548 | 549 | var createHmacOutputMethod = function (outputType, is224) { 550 | return function (key, message) { 551 | return new HmacSha256(key, is224, true).update(message)[outputType](); 552 | }; 553 | }; 554 | 555 | var createHmacMethod = function (is224) { 556 | var method = createHmacOutputMethod('hex', is224); 557 | method.create = function (key) { 558 | return new HmacSha256(key, is224); 559 | }; 560 | method.update = function (key, message) { 561 | return method.create(key).update(message); 562 | }; 563 | for (var i = 0; i < OUTPUT_TYPES.length; ++i) { 564 | var type = OUTPUT_TYPES[i]; 565 | method[type] = createHmacOutputMethod(type, is224); 566 | } 567 | return method; 568 | }; 569 | 570 | function Sha256(is224, sharedMemory) { 571 | if (sharedMemory) { 572 | blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = 573 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 574 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 575 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 576 | this.blocks = blocks; 577 | } else { 578 | this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 579 | } 580 | 581 | if (is224) { 582 | this.h0 = 0xc1059ed8; 583 | this.h1 = 0x367cd507; 584 | this.h2 = 0x3070dd17; 585 | this.h3 = 0xf70e5939; 586 | this.h4 = 0xffc00b31; 587 | this.h5 = 0x68581511; 588 | this.h6 = 0x64f98fa7; 589 | this.h7 = 0xbefa4fa4; 590 | } else { // 256 591 | this.h0 = 0x6a09e667; 592 | this.h1 = 0xbb67ae85; 593 | this.h2 = 0x3c6ef372; 594 | this.h3 = 0xa54ff53a; 595 | this.h4 = 0x510e527f; 596 | this.h5 = 0x9b05688c; 597 | this.h6 = 0x1f83d9ab; 598 | this.h7 = 0x5be0cd19; 599 | } 600 | 601 | this.block = this.start = this.bytes = this.hBytes = 0; 602 | this.finalized = this.hashed = false; 603 | this.first = true; 604 | this.is224 = is224; 605 | } 606 | 607 | Sha256.prototype.update = function (message) { 608 | if (this.finalized) { 609 | return; 610 | } 611 | var notString, type = typeof message; 612 | if (type !== 'string') { 613 | if (type === 'object') { 614 | if (message === null) { 615 | throw new Error(ERROR); 616 | } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { 617 | message = new Uint8Array(message); 618 | } else if (!Array.isArray(message)) { 619 | if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { 620 | throw new Error(ERROR); 621 | } 622 | } 623 | } else { 624 | throw new Error(ERROR); 625 | } 626 | notString = true; 627 | } 628 | var code, index = 0, i, length = message.length, blocks = this.blocks; 629 | 630 | while (index < length) { 631 | if (this.hashed) { 632 | this.hashed = false; 633 | blocks[0] = this.block; 634 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = 635 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 636 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 637 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 638 | } 639 | 640 | if (notString) { 641 | for (i = this.start; index < length && i < 64; ++index) { 642 | blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; 643 | } 644 | } else { 645 | for (i = this.start; index < length && i < 64; ++index) { 646 | code = message.charCodeAt(index); 647 | if (code < 0x80) { 648 | blocks[i >> 2] |= code << SHIFT[i++ & 3]; 649 | } else if (code < 0x800) { 650 | blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; 651 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 652 | } else if (code < 0xd800 || code >= 0xe000) { 653 | blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; 654 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 655 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 656 | } else { 657 | code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); 658 | blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; 659 | blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; 660 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 661 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 662 | } 663 | } 664 | } 665 | 666 | this.lastByteIndex = i; 667 | this.bytes += i - this.start; 668 | if (i >= 64) { 669 | this.block = blocks[16]; 670 | this.start = i - 64; 671 | this.hash(); 672 | this.hashed = true; 673 | } else { 674 | this.start = i; 675 | } 676 | } 677 | if (this.bytes > 4294967295) { 678 | this.hBytes += this.bytes / 4294967296 << 0; 679 | this.bytes = this.bytes % 4294967296; 680 | } 681 | return this; 682 | }; 683 | 684 | Sha256.prototype.finalize = function () { 685 | if (this.finalized) { 686 | return; 687 | } 688 | this.finalized = true; 689 | var blocks = this.blocks, i = this.lastByteIndex; 690 | blocks[16] = this.block; 691 | blocks[i >> 2] |= EXTRA[i & 3]; 692 | this.block = blocks[16]; 693 | if (i >= 56) { 694 | if (!this.hashed) { 695 | this.hash(); 696 | } 697 | blocks[0] = this.block; 698 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = 699 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 700 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 701 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 702 | } 703 | blocks[14] = this.hBytes << 3 | this.bytes >>> 29; 704 | blocks[15] = this.bytes << 3; 705 | this.hash(); 706 | }; 707 | 708 | Sha256.prototype.hash = function () { 709 | var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4, f = this.h5, g = this.h6, 710 | h = this.h7, blocks = this.blocks, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc; 711 | 712 | for (j = 16; j < 64; ++j) { 713 | // rightrotate 714 | t1 = blocks[j - 15]; 715 | s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); 716 | t1 = blocks[j - 2]; 717 | s1 = ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10); 718 | blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0; 719 | } 720 | 721 | bc = b & c; 722 | for (j = 0; j < 64; j += 4) { 723 | if (this.first) { 724 | if (this.is224) { 725 | ab = 300032; 726 | t1 = blocks[0] - 1413257819; 727 | h = t1 - 150054599 << 0; 728 | d = t1 + 24177077 << 0; 729 | } else { 730 | ab = 704751109; 731 | t1 = blocks[0] - 210244248; 732 | h = t1 - 1521486534 << 0; 733 | d = t1 + 143694565 << 0; 734 | } 735 | this.first = false; 736 | } else { 737 | s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)); 738 | s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)); 739 | ab = a & b; 740 | maj = ab ^ (a & c) ^ bc; 741 | ch = (e & f) ^ (~e & g); 742 | t1 = h + s1 + ch + K[j] + blocks[j]; 743 | t2 = s0 + maj; 744 | h = d + t1 << 0; 745 | d = t1 + t2 << 0; 746 | } 747 | s0 = ((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10)); 748 | s1 = ((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7)); 749 | da = d & a; 750 | maj = da ^ (d & b) ^ ab; 751 | ch = (h & e) ^ (~h & f); 752 | t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; 753 | t2 = s0 + maj; 754 | g = c + t1 << 0; 755 | c = t1 + t2 << 0; 756 | s0 = ((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10)); 757 | s1 = ((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7)); 758 | cd = c & d; 759 | maj = cd ^ (c & a) ^ da; 760 | ch = (g & h) ^ (~g & e); 761 | t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; 762 | t2 = s0 + maj; 763 | f = b + t1 << 0; 764 | b = t1 + t2 << 0; 765 | s0 = ((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10)); 766 | s1 = ((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7)); 767 | bc = b & c; 768 | maj = bc ^ (b & d) ^ cd; 769 | ch = (f & g) ^ (~f & h); 770 | t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; 771 | t2 = s0 + maj; 772 | e = a + t1 << 0; 773 | a = t1 + t2 << 0; 774 | } 775 | 776 | this.h0 = this.h0 + a << 0; 777 | this.h1 = this.h1 + b << 0; 778 | this.h2 = this.h2 + c << 0; 779 | this.h3 = this.h3 + d << 0; 780 | this.h4 = this.h4 + e << 0; 781 | this.h5 = this.h5 + f << 0; 782 | this.h6 = this.h6 + g << 0; 783 | this.h7 = this.h7 + h << 0; 784 | }; 785 | 786 | Sha256.prototype.hex = function () { 787 | this.finalize(); 788 | 789 | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, 790 | h6 = this.h6, h7 = this.h7; 791 | 792 | var hex = HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] + 793 | HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + 794 | HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + 795 | HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + 796 | HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + 797 | HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + 798 | HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + 799 | HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + 800 | HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + 801 | HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + 802 | HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + 803 | HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + 804 | HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] + 805 | HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + 806 | HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + 807 | HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + 808 | HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] + 809 | HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] + 810 | HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] + 811 | HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F] + 812 | HEX_CHARS[(h5 >> 28) & 0x0F] + HEX_CHARS[(h5 >> 24) & 0x0F] + 813 | HEX_CHARS[(h5 >> 20) & 0x0F] + HEX_CHARS[(h5 >> 16) & 0x0F] + 814 | HEX_CHARS[(h5 >> 12) & 0x0F] + HEX_CHARS[(h5 >> 8) & 0x0F] + 815 | HEX_CHARS[(h5 >> 4) & 0x0F] + HEX_CHARS[h5 & 0x0F] + 816 | HEX_CHARS[(h6 >> 28) & 0x0F] + HEX_CHARS[(h6 >> 24) & 0x0F] + 817 | HEX_CHARS[(h6 >> 20) & 0x0F] + HEX_CHARS[(h6 >> 16) & 0x0F] + 818 | HEX_CHARS[(h6 >> 12) & 0x0F] + HEX_CHARS[(h6 >> 8) & 0x0F] + 819 | HEX_CHARS[(h6 >> 4) & 0x0F] + HEX_CHARS[h6 & 0x0F]; 820 | if (!this.is224) { 821 | hex += HEX_CHARS[(h7 >> 28) & 0x0F] + HEX_CHARS[(h7 >> 24) & 0x0F] + 822 | HEX_CHARS[(h7 >> 20) & 0x0F] + HEX_CHARS[(h7 >> 16) & 0x0F] + 823 | HEX_CHARS[(h7 >> 12) & 0x0F] + HEX_CHARS[(h7 >> 8) & 0x0F] + 824 | HEX_CHARS[(h7 >> 4) & 0x0F] + HEX_CHARS[h7 & 0x0F]; 825 | } 826 | return hex; 827 | }; 828 | 829 | Sha256.prototype.toString = Sha256.prototype.hex; 830 | 831 | Sha256.prototype.digest = function () { 832 | this.finalize(); 833 | 834 | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, 835 | h6 = this.h6, h7 = this.h7; 836 | 837 | var arr = [ 838 | (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF, 839 | (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF, 840 | (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF, 841 | (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF, 842 | (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF, 843 | (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, h5 & 0xFF, 844 | (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, h6 & 0xFF 845 | ]; 846 | if (!this.is224) { 847 | arr.push((h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, h7 & 0xFF); 848 | } 849 | return arr; 850 | }; 851 | 852 | Sha256.prototype.array = Sha256.prototype.digest; 853 | 854 | Sha256.prototype.arrayBuffer = function () { 855 | this.finalize(); 856 | 857 | var buffer = new ArrayBuffer(this.is224 ? 28 : 32); 858 | var dataView = new DataView(buffer); 859 | dataView.setUint32(0, this.h0); 860 | dataView.setUint32(4, this.h1); 861 | dataView.setUint32(8, this.h2); 862 | dataView.setUint32(12, this.h3); 863 | dataView.setUint32(16, this.h4); 864 | dataView.setUint32(20, this.h5); 865 | dataView.setUint32(24, this.h6); 866 | if (!this.is224) { 867 | dataView.setUint32(28, this.h7); 868 | } 869 | return buffer; 870 | }; 871 | 872 | function HmacSha256(key, is224, sharedMemory) { 873 | var i, type = typeof key; 874 | if (type === 'string') { 875 | var bytes = [], length = key.length, index = 0, code; 876 | for (i = 0; i < length; ++i) { 877 | code = key.charCodeAt(i); 878 | if (code < 0x80) { 879 | bytes[index++] = code; 880 | } else if (code < 0x800) { 881 | bytes[index++] = (0xc0 | (code >> 6)); 882 | bytes[index++] = (0x80 | (code & 0x3f)); 883 | } else if (code < 0xd800 || code >= 0xe000) { 884 | bytes[index++] = (0xe0 | (code >> 12)); 885 | bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); 886 | bytes[index++] = (0x80 | (code & 0x3f)); 887 | } else { 888 | code = 0x10000 + (((code & 0x3ff) << 10) | (key.charCodeAt(++i) & 0x3ff)); 889 | bytes[index++] = (0xf0 | (code >> 18)); 890 | bytes[index++] = (0x80 | ((code >> 12) & 0x3f)); 891 | bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); 892 | bytes[index++] = (0x80 | (code & 0x3f)); 893 | } 894 | } 895 | key = bytes; 896 | } else { 897 | if (type === 'object') { 898 | if (key === null) { 899 | throw new Error(ERROR); 900 | } else if (ARRAY_BUFFER && key.constructor === ArrayBuffer) { 901 | key = new Uint8Array(key); 902 | } else if (!Array.isArray(key)) { 903 | if (!ARRAY_BUFFER || !ArrayBuffer.isView(key)) { 904 | throw new Error(ERROR); 905 | } 906 | } 907 | } else { 908 | throw new Error(ERROR); 909 | } 910 | } 911 | 912 | if (key.length > 64) { 913 | key = (new Sha256(is224, true)).update(key).array(); 914 | } 915 | 916 | var oKeyPad = [], iKeyPad = []; 917 | for (i = 0; i < 64; ++i) { 918 | var b = key[i] || 0; 919 | oKeyPad[i] = 0x5c ^ b; 920 | iKeyPad[i] = 0x36 ^ b; 921 | } 922 | 923 | Sha256.call(this, is224, sharedMemory); 924 | 925 | this.update(iKeyPad); 926 | this.oKeyPad = oKeyPad; 927 | this.inner = true; 928 | this.sharedMemory = sharedMemory; 929 | } 930 | HmacSha256.prototype = new Sha256(); 931 | 932 | HmacSha256.prototype.finalize = function () { 933 | Sha256.prototype.finalize.call(this); 934 | if (this.inner) { 935 | this.inner = false; 936 | var innerHash = this.array(); 937 | Sha256.call(this, this.is224, this.sharedMemory); 938 | this.update(this.oKeyPad); 939 | this.update(innerHash); 940 | Sha256.prototype.finalize.call(this); 941 | } 942 | }; 943 | 944 | var exports = createMethod(); 945 | exports.sha256 = exports; 946 | exports.sha224 = createMethod(true); 947 | exports.sha256.hmac = createHmacMethod(); 948 | exports.sha224.hmac = createHmacMethod(true); 949 | 950 | if (COMMON_JS) { 951 | module.exports = exports; 952 | } else { 953 | root.sha256 = exports.sha256; 954 | root.sha224 = exports.sha224; 955 | if (AMD) { 956 | define(function () { 957 | return exports; 958 | }); 959 | } 960 | } 961 | })(); 962 | 963 | /* 964 | * [genanki]{@link https://github.com/kerrickstaley/genanki} 965 | * @copyright Copyright (c) Kerrick Staley 2021 966 | * @license The MIT License 967 | */ 968 | 969 | const APKG_SCHEMA = ` 970 | PRAGMA foreign_keys=OFF; 971 | BEGIN TRANSACTION; 972 | 973 | CREATE TABLE col ( 974 | id integer primary key, 975 | crt integer not null, 976 | mod integer not null, 977 | scm integer not null, 978 | ver integer not null, 979 | dty integer not null, 980 | usn integer not null, 981 | ls integer not null, 982 | conf text not null, 983 | models text not null, 984 | decks text not null, 985 | dconf text not null, 986 | tags text not null 987 | ); 988 | CREATE TABLE notes ( 989 | id integer primary key, /* 0 */ 990 | guid text not null, /* 1 */ 991 | mid integer not null, /* 2 */ 992 | mod integer not null, /* 3 */ 993 | usn integer not null, /* 4 */ 994 | tags text not null, /* 5 */ 995 | flds text not null, /* 6 */ 996 | sfld integer not null, /* 7 */ 997 | csum integer not null, /* 8 */ 998 | flags integer not null, /* 9 */ 999 | data text not null /* 10 */ 1000 | ); 1001 | CREATE TABLE cards ( 1002 | id integer primary key, /* 0 */ 1003 | nid integer not null, /* 1 */ 1004 | did integer not null, /* 2 */ 1005 | ord integer not null, /* 3 */ 1006 | mod integer not null, /* 4 */ 1007 | usn integer not null, /* 5 */ 1008 | type integer not null, /* 6 */ 1009 | queue integer not null, /* 7 */ 1010 | due integer not null, /* 8 */ 1011 | ivl integer not null, /* 9 */ 1012 | factor integer not null, /* 10 */ 1013 | reps integer not null, /* 11 */ 1014 | lapses integer not null, /* 12 */ 1015 | left integer not null, /* 13 */ 1016 | odue integer not null, /* 14 */ 1017 | odid integer not null, /* 15 */ 1018 | flags integer not null, /* 16 */ 1019 | data text not null /* 17 */ 1020 | ); 1021 | CREATE TABLE revlog ( 1022 | id integer primary key, 1023 | cid integer not null, 1024 | usn integer not null, 1025 | ease integer not null, 1026 | ivl integer not null, 1027 | lastIvl integer not null, 1028 | factor integer not null, 1029 | time integer not null, 1030 | type integer not null 1031 | ); 1032 | CREATE TABLE graves ( 1033 | usn integer not null, 1034 | oid integer not null, 1035 | type integer not null 1036 | ); 1037 | CREATE INDEX ix_notes_usn on notes (usn); 1038 | CREATE INDEX ix_cards_usn on cards (usn); 1039 | CREATE INDEX ix_revlog_usn on revlog (usn); 1040 | CREATE INDEX ix_cards_nid on cards (nid); 1041 | CREATE INDEX ix_cards_sched on cards (did, queue, due); 1042 | CREATE INDEX ix_revlog_cid on revlog (cid); 1043 | CREATE INDEX ix_notes_csum on notes (csum); 1044 | COMMIT; 1045 | `; 1046 | 1047 | var APKG_COL = ` 1048 | INSERT INTO col VALUES( 1049 | null, 1050 | 1411124400, 1051 | 1425279151694, 1052 | 1425279151690, 1053 | 11, 1054 | 0, 1055 | 0, 1056 | 0, 1057 | '{ 1058 | "activeDecks": [ 1059 | 1 1060 | ], 1061 | "addToCur": true, 1062 | "collapseTime": 1200, 1063 | "curDeck": 1, 1064 | "curModel": "1425279151691", 1065 | "dueCounts": true, 1066 | "estTimes": true, 1067 | "newBury": true, 1068 | "newSpread": 0, 1069 | "nextPos": 1, 1070 | "sortBackwards": false, 1071 | "sortType": "noteFld", 1072 | "timeLim": 0 1073 | }', 1074 | '{}', 1075 | '{ 1076 | "1": { 1077 | "collapsed": false, 1078 | "conf": 1, 1079 | "desc": "", 1080 | "dyn": 0, 1081 | "extendNew": 10, 1082 | "extendRev": 50, 1083 | "id": 1, 1084 | "lrnToday": [ 1085 | 0, 1086 | 0 1087 | ], 1088 | "mod": 1425279151, 1089 | "name": "Default", 1090 | "newToday": [ 1091 | 0, 1092 | 0 1093 | ], 1094 | "revToday": [ 1095 | 0, 1096 | 0 1097 | ], 1098 | "timeToday": [ 1099 | 0, 1100 | 0 1101 | ], 1102 | "usn": 0 1103 | } 1104 | }', 1105 | '{ 1106 | "1": { 1107 | "autoplay": true, 1108 | "id": 1, 1109 | "lapse": { 1110 | "delays": [ 1111 | 10 1112 | ], 1113 | "leechAction": 0, 1114 | "leechFails": 8, 1115 | "minInt": 1, 1116 | "mult": 0 1117 | }, 1118 | "maxTaken": 60, 1119 | "mod": 0, 1120 | "name": "Default", 1121 | "new": { 1122 | "bury": true, 1123 | "delays": [ 1124 | 1, 1125 | 10 1126 | ], 1127 | "initialFactor": 2500, 1128 | "ints": [ 1129 | 1, 1130 | 4, 1131 | 7 1132 | ], 1133 | "order": 1, 1134 | "perDay": 20, 1135 | "separate": true 1136 | }, 1137 | "replayq": true, 1138 | "rev": { 1139 | "bury": true, 1140 | "ease4": 1.3, 1141 | "fuzz": 0.05, 1142 | "ivlFct": 1, 1143 | "maxIvl": 36500, 1144 | "minSpace": 1, 1145 | "perDay": 100 1146 | }, 1147 | "timer": 0, 1148 | "usn": 0 1149 | } 1150 | }', 1151 | '{}' 1152 | ); 1153 | `; 1154 | -------------------------------------------------------------------------------- /docs/demo/js/codemirror/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | direction: ltr; 9 | } 10 | 11 | /* PADDING */ 12 | 13 | .CodeMirror-lines { 14 | padding: 4px 0; /* Vertical padding around content */ 15 | } 16 | .CodeMirror pre.CodeMirror-line, 17 | .CodeMirror pre.CodeMirror-line-like { 18 | padding: 0 4px; /* Horizontal padding of content */ 19 | } 20 | 21 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 22 | background-color: white; /* The little square between H and V scrollbars */ 23 | } 24 | 25 | /* GUTTER */ 26 | 27 | .CodeMirror-gutters { 28 | border-right: 1px solid #ddd; 29 | background-color: #f7f7f7; 30 | white-space: nowrap; 31 | } 32 | .CodeMirror-linenumbers {} 33 | .CodeMirror-linenumber { 34 | padding: 0 3px 0 5px; 35 | min-width: 20px; 36 | text-align: right; 37 | color: #999; 38 | white-space: nowrap; 39 | } 40 | 41 | .CodeMirror-guttermarker { color: black; } 42 | .CodeMirror-guttermarker-subtle { color: #999; } 43 | 44 | /* CURSOR */ 45 | 46 | .CodeMirror-cursor { 47 | border-left: 1px solid black; 48 | border-right: none; 49 | width: 0; 50 | } 51 | /* Shown when moving in bi-directional text */ 52 | .CodeMirror div.CodeMirror-secondarycursor { 53 | border-left: 1px solid silver; 54 | } 55 | .cm-fat-cursor .CodeMirror-cursor { 56 | width: auto; 57 | border: 0 !important; 58 | background: #7e7; 59 | } 60 | .cm-fat-cursor div.CodeMirror-cursors { 61 | z-index: 1; 62 | } 63 | .cm-fat-cursor-mark { 64 | background-color: rgba(20, 255, 20, 0.5); 65 | -webkit-animation: blink 1.06s steps(1) infinite; 66 | -moz-animation: blink 1.06s steps(1) infinite; 67 | animation: blink 1.06s steps(1) infinite; 68 | } 69 | .cm-animate-fat-cursor { 70 | width: auto; 71 | border: 0; 72 | -webkit-animation: blink 1.06s steps(1) infinite; 73 | -moz-animation: blink 1.06s steps(1) infinite; 74 | animation: blink 1.06s steps(1) infinite; 75 | background-color: #7e7; 76 | } 77 | @-moz-keyframes blink { 78 | 0% {} 79 | 50% { background-color: transparent; } 80 | 100% {} 81 | } 82 | @-webkit-keyframes blink { 83 | 0% {} 84 | 50% { background-color: transparent; } 85 | 100% {} 86 | } 87 | @keyframes blink { 88 | 0% {} 89 | 50% { background-color: transparent; } 90 | 100% {} 91 | } 92 | 93 | /* Can style cursor different in overwrite (non-insert) mode */ 94 | .CodeMirror-overwrite .CodeMirror-cursor {} 95 | 96 | .cm-tab { display: inline-block; text-decoration: inherit; } 97 | 98 | .CodeMirror-rulers { 99 | position: absolute; 100 | left: 0; right: 0; top: -50px; bottom: 0; 101 | overflow: hidden; 102 | } 103 | .CodeMirror-ruler { 104 | border-left: 1px solid #ccc; 105 | top: 0; bottom: 0; 106 | position: absolute; 107 | } 108 | 109 | /* DEFAULT THEME */ 110 | 111 | .cm-s-default .cm-header {color: blue;} 112 | .cm-s-default .cm-quote {color: #090;} 113 | .cm-negative {color: #d44;} 114 | .cm-positive {color: #292;} 115 | .cm-header, .cm-strong {font-weight: bold;} 116 | .cm-em {font-style: italic;} 117 | .cm-link {text-decoration: underline;} 118 | .cm-strikethrough {text-decoration: line-through;} 119 | 120 | .cm-s-default .cm-keyword {color: #708;} 121 | .cm-s-default .cm-atom {color: #219;} 122 | .cm-s-default .cm-number {color: #164;} 123 | .cm-s-default .cm-def {color: #00f;} 124 | .cm-s-default .cm-variable, 125 | .cm-s-default .cm-punctuation, 126 | .cm-s-default .cm-property, 127 | .cm-s-default .cm-operator {} 128 | .cm-s-default .cm-variable-2 {color: #05a;} 129 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} 130 | .cm-s-default .cm-comment {color: #a50;} 131 | .cm-s-default .cm-string {color: #a11;} 132 | .cm-s-default .cm-string-2 {color: #f50;} 133 | .cm-s-default .cm-meta {color: #555;} 134 | .cm-s-default .cm-qualifier {color: #555;} 135 | .cm-s-default .cm-builtin {color: #30a;} 136 | .cm-s-default .cm-bracket {color: #997;} 137 | .cm-s-default .cm-tag {color: #170;} 138 | .cm-s-default .cm-attribute {color: #00c;} 139 | .cm-s-default .cm-hr {color: #999;} 140 | .cm-s-default .cm-link {color: #00c;} 141 | 142 | .cm-s-default .cm-error {color: #f00;} 143 | .cm-invalidchar {color: #f00;} 144 | 145 | .CodeMirror-composing { border-bottom: 2px solid; } 146 | 147 | /* Default styles for common addons */ 148 | 149 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} 150 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} 151 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 152 | .CodeMirror-activeline-background {background: #e8f2ff;} 153 | 154 | /* STOP */ 155 | 156 | /* The rest of this file contains styles related to the mechanics of 157 | the editor. You probably shouldn't touch them. */ 158 | 159 | .CodeMirror { 160 | position: relative; 161 | overflow: hidden; 162 | background: white; 163 | } 164 | 165 | .CodeMirror-scroll { 166 | overflow: scroll !important; /* Things will break if this is overridden */ 167 | /* 50px is the magic margin used to hide the element's real scrollbars */ 168 | /* See overflow: hidden in .CodeMirror */ 169 | margin-bottom: -50px; margin-right: -50px; 170 | padding-bottom: 50px; 171 | height: 100%; 172 | outline: none; /* Prevent dragging from highlighting the element */ 173 | position: relative; 174 | } 175 | .CodeMirror-sizer { 176 | position: relative; 177 | border-right: 50px solid transparent; 178 | } 179 | 180 | /* The fake, visible scrollbars. Used to force redraw during scrolling 181 | before actual scrolling happens, thus preventing shaking and 182 | flickering artifacts. */ 183 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 184 | position: absolute; 185 | z-index: 6; 186 | display: none; 187 | outline: none; 188 | } 189 | .CodeMirror-vscrollbar { 190 | right: 0; top: 0; 191 | overflow-x: hidden; 192 | overflow-y: scroll; 193 | } 194 | .CodeMirror-hscrollbar { 195 | bottom: 0; left: 0; 196 | overflow-y: hidden; 197 | overflow-x: scroll; 198 | } 199 | .CodeMirror-scrollbar-filler { 200 | right: 0; bottom: 0; 201 | } 202 | .CodeMirror-gutter-filler { 203 | left: 0; bottom: 0; 204 | } 205 | 206 | .CodeMirror-gutters { 207 | position: absolute; left: 0; top: 0; 208 | min-height: 100%; 209 | z-index: 3; 210 | } 211 | .CodeMirror-gutter { 212 | white-space: normal; 213 | height: 100%; 214 | display: inline-block; 215 | vertical-align: top; 216 | margin-bottom: -50px; 217 | } 218 | .CodeMirror-gutter-wrapper { 219 | position: absolute; 220 | z-index: 4; 221 | background: none !important; 222 | border: none !important; 223 | } 224 | .CodeMirror-gutter-background { 225 | position: absolute; 226 | top: 0; bottom: 0; 227 | z-index: 4; 228 | } 229 | .CodeMirror-gutter-elt { 230 | position: absolute; 231 | cursor: default; 232 | z-index: 4; 233 | } 234 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent } 235 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } 236 | 237 | .CodeMirror-lines { 238 | cursor: text; 239 | min-height: 1px; /* prevents collapsing before first draw */ 240 | } 241 | .CodeMirror pre.CodeMirror-line, 242 | .CodeMirror pre.CodeMirror-line-like { 243 | /* Reset some styles that the rest of the page might have set */ 244 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 245 | border-width: 0; 246 | background: transparent; 247 | font-family: inherit; 248 | font-size: inherit; 249 | margin: 0; 250 | white-space: pre; 251 | word-wrap: normal; 252 | line-height: inherit; 253 | color: inherit; 254 | z-index: 2; 255 | position: relative; 256 | overflow: visible; 257 | -webkit-tap-highlight-color: transparent; 258 | -webkit-font-variant-ligatures: contextual; 259 | font-variant-ligatures: contextual; 260 | } 261 | .CodeMirror-wrap pre.CodeMirror-line, 262 | .CodeMirror-wrap pre.CodeMirror-line-like { 263 | word-wrap: break-word; 264 | white-space: pre-wrap; 265 | word-break: normal; 266 | } 267 | 268 | .CodeMirror-linebackground { 269 | position: absolute; 270 | left: 0; right: 0; top: 0; bottom: 0; 271 | z-index: 0; 272 | } 273 | 274 | .CodeMirror-linewidget { 275 | position: relative; 276 | z-index: 2; 277 | padding: 0.1px; /* Force widget margins to stay inside of the container */ 278 | } 279 | 280 | .CodeMirror-widget {} 281 | 282 | .CodeMirror-rtl pre { direction: rtl; } 283 | 284 | .CodeMirror-code { 285 | outline: none; 286 | } 287 | 288 | /* Force content-box sizing for the elements where we expect it */ 289 | .CodeMirror-scroll, 290 | .CodeMirror-sizer, 291 | .CodeMirror-gutter, 292 | .CodeMirror-gutters, 293 | .CodeMirror-linenumber { 294 | -moz-box-sizing: content-box; 295 | box-sizing: content-box; 296 | } 297 | 298 | .CodeMirror-measure { 299 | position: absolute; 300 | width: 100%; 301 | height: 0; 302 | overflow: hidden; 303 | visibility: hidden; 304 | } 305 | 306 | .CodeMirror-cursor { 307 | position: absolute; 308 | pointer-events: none; 309 | } 310 | .CodeMirror-measure pre { position: static; } 311 | 312 | div.CodeMirror-cursors { 313 | visibility: hidden; 314 | position: relative; 315 | z-index: 3; 316 | } 317 | div.CodeMirror-dragcursors { 318 | visibility: visible; 319 | } 320 | 321 | .CodeMirror-focused div.CodeMirror-cursors { 322 | visibility: visible; 323 | } 324 | 325 | .CodeMirror-selected { background: #d9d9d9; } 326 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 327 | .CodeMirror-crosshair { cursor: crosshair; } 328 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 329 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 330 | 331 | .cm-searching { 332 | background-color: #ffa; 333 | background-color: rgba(255, 255, 0, .4); 334 | } 335 | 336 | /* Used to force a border model for a node */ 337 | .cm-force-border { padding-right: .1px; } 338 | 339 | @media print { 340 | /* Hide the cursor when printing */ 341 | .CodeMirror div.CodeMirror-cursors { 342 | visibility: hidden; 343 | } 344 | } 345 | 346 | /* See issue #2901 */ 347 | .cm-tab-wrap-hack:after { content: ''; } 348 | 349 | /* Help users use markselection to safely style text background */ 350 | span.CodeMirror-selectedtext { background: none; } 351 | -------------------------------------------------------------------------------- /docs/demo/js/filesaver/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | (function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)}); 2 | 3 | //# sourceMappingURL=FileSaver.min.js.map -------------------------------------------------------------------------------- /docs/demo/js/filesaver/FileSaver.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/FileSaver.js"],"names":[],"mappings":"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","file":"FileSaver.min.js","sourcesContent":["/*\n* FileSaver.js\n* A saveAs() FileSaver implementation.\n*\n* By Eli Grey, http://eligrey.com\n*\n* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)\n* source : http://purl.eligrey.com/github/FileSaver.js\n*/\n\n// The one and only way of getting global scope in all environments\n// https://stackoverflow.com/q/3277182/1008999\nvar _global = typeof window === 'object' && window.window === window\n ? window : typeof self === 'object' && self.self === self\n ? self : typeof global === 'object' && global.global === global\n ? global\n : this\n\nfunction bom (blob, opts) {\n if (typeof opts === 'undefined') opts = { autoBom: false }\n else if (typeof opts !== 'object') {\n console.warn('Deprecated: Expected third argument to be a object')\n opts = { autoBom: !opts }\n }\n\n // prepend BOM for UTF-8 XML and text/* types (including HTML)\n // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF\n if (opts.autoBom && /^\\s*(?:text\\/\\S*|application\\/xml|\\S*\\/\\S*\\+xml)\\s*;.*charset\\s*=\\s*utf-8/i.test(blob.type)) {\n return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })\n }\n return blob\n}\n\nfunction download (url, name, opts) {\n var xhr = new XMLHttpRequest()\n xhr.open('GET', url)\n xhr.responseType = 'blob'\n xhr.onload = function () {\n saveAs(xhr.response, name, opts)\n }\n xhr.onerror = function () {\n console.error('could not download file')\n }\n xhr.send()\n}\n\nfunction corsEnabled (url) {\n var xhr = new XMLHttpRequest()\n // use sync to avoid popup blocker\n xhr.open('HEAD', url, false)\n try {\n xhr.send()\n } catch (e) {}\n return xhr.status >= 200 && xhr.status <= 299\n}\n\n// `a.click()` doesn't work for all browsers (#465)\nfunction click (node) {\n try {\n node.dispatchEvent(new MouseEvent('click'))\n } catch (e) {\n var evt = document.createEvent('MouseEvents')\n evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,\n 20, false, false, false, false, 0, null)\n node.dispatchEvent(evt)\n }\n}\n\n// Detect WebView inside a native macOS app by ruling out all browsers\n// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too\n// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos\nvar isMacOSWebView = /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent)\n\nvar saveAs = _global.saveAs || (\n // probably in some web worker\n (typeof window !== 'object' || window !== _global)\n ? function saveAs () { /* noop */ }\n\n // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView\n : ('download' in HTMLAnchorElement.prototype && !isMacOSWebView)\n ? function saveAs (blob, name, opts) {\n var URL = _global.URL || _global.webkitURL\n var a = document.createElement('a')\n name = name || blob.name || 'download'\n\n a.download = name\n a.rel = 'noopener' // tabnabbing\n\n // TODO: detect chrome extensions & packaged apps\n // a.target = '_blank'\n\n if (typeof blob === 'string') {\n // Support regular links\n a.href = blob\n if (a.origin !== location.origin) {\n corsEnabled(a.href)\n ? download(blob, name, opts)\n : click(a, a.target = '_blank')\n } else {\n click(a)\n }\n } else {\n // Support blobs\n a.href = URL.createObjectURL(blob)\n setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s\n setTimeout(function () { click(a) }, 0)\n }\n }\n\n // Use msSaveOrOpenBlob as a second approach\n : 'msSaveOrOpenBlob' in navigator\n ? function saveAs (blob, name, opts) {\n name = name || blob.name || 'download'\n\n if (typeof blob === 'string') {\n if (corsEnabled(blob)) {\n download(blob, name, opts)\n } else {\n var a = document.createElement('a')\n a.href = blob\n a.target = '_blank'\n setTimeout(function () { click(a) })\n }\n } else {\n navigator.msSaveOrOpenBlob(bom(blob, opts), name)\n }\n }\n\n // Fallback to using FileReader and a popup\n : function saveAs (blob, name, opts, popup) {\n // Open a popup immediately do go around popup blocker\n // Mostly only available on user interaction and the fileReader is async so...\n popup = popup || open('', '_blank')\n if (popup) {\n popup.document.title =\n popup.document.body.innerText = 'downloading...'\n }\n\n if (typeof blob === 'string') return download(blob, name, opts)\n\n var force = blob.type === 'application/octet-stream'\n var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari\n var isChromeIOS = /CriOS\\/[\\d]+/.test(navigator.userAgent)\n\n if ((isChromeIOS || (force && isSafari) || isMacOSWebView) && typeof FileReader !== 'undefined') {\n // Safari doesn't allow downloading of blob URLs\n var reader = new FileReader()\n reader.onloadend = function () {\n var url = reader.result\n url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')\n if (popup) popup.location.href = url\n else location = url\n popup = null // reverse-tabnabbing #460\n }\n reader.readAsDataURL(blob)\n } else {\n var URL = _global.URL || _global.webkitURL\n var url = URL.createObjectURL(blob)\n if (popup) popup.location = url\n else location.href = url\n popup = null // reverse-tabnabbing #460\n setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s\n }\n }\n)\n\n_global.saveAs = saveAs.saveAs = saveAs\n\nif (typeof module !== 'undefined') {\n module.exports = saveAs;\n}\n"]} -------------------------------------------------------------------------------- /docs/demo/js/fsimage.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krmanik/genanki-js/7a88bc5d3a22ae375309080daffc36f4e11e51cd/docs/demo/js/fsimage.js -------------------------------------------------------------------------------- /docs/demo/js/index.js: -------------------------------------------------------------------------------- 1 | // The `initSqlJs` function is globally provided by all of the main dist files if loaded in the browser. 2 | // We must specify this locateFile function if we are loading a wasm file from anywhere other than the current html page's folder. 3 | 4 | var SQL; 5 | initSqlJs().then(function (sql) { 6 | //Create the database 7 | SQL = sql; 8 | }); -------------------------------------------------------------------------------- /docs/demo/js/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Test basic with 2 notes 3 | */ 4 | function test1() { 5 | const m = new Model({ 6 | name: "Basic", 7 | id: "1542906796044", 8 | flds: [ 9 | { name: "Front" }, 10 | { name: "Back" } 11 | ], 12 | req: [ 13 | [0, "all", [0]], 14 | ], 15 | tmpls: [ 16 | { 17 | name: "Card 1", 18 | qfmt: "{{Front}}", 19 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 20 | } 21 | ], 22 | }) 23 | 24 | const d = new Deck(1347617346765, "hi") 25 | 26 | d.addNote(m.note(['hello', 'world'])) 27 | d.addNote(m.note(['this is test', 'for anki'])) 28 | 29 | const p = new Package() 30 | p.addDeck(d) 31 | p.writeToFile('deck.apkg') 32 | } 33 | 34 | /* 35 | Test basic with 10 notes in loop 36 | */ 37 | function test10() { 38 | const m = new Model({ 39 | name: "Basic", 40 | id: "1542906796044", 41 | flds: [ 42 | { name: "Front" }, 43 | { name: "Back" } 44 | ], 45 | req: [ 46 | [0, "all", [0]], 47 | ], 48 | tmpls: [ 49 | { 50 | name: "Card 1", 51 | qfmt: "{{Front}}", 52 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 53 | } 54 | ], 55 | }) 56 | 57 | const d = new Deck(1347617346765, "hi") 58 | 59 | for (i = 0; i < 10; i++) { 60 | d.addNote(m.note(['front note ' + i, 'back note ' + i])) 61 | } 62 | 63 | const p = new Package() 64 | p.addDeck(d) 65 | p.writeToFile('deck.apkg') 66 | } 67 | 68 | /* 69 | Test basic with 2 chinese decks 70 | */ 71 | function test2() { 72 | const m = new Model({ 73 | name: "Basic", 74 | id: "1542906796045", 75 | flds: [ 76 | { name: "Simplified" }, 77 | { name: "Traditional" }, 78 | { name: "Pinyin" }, 79 | { name: "Meaning" }, 80 | ], 81 | req: [ 82 | [0, "all", [0]], 83 | ], 84 | tmpls: [ 85 | { 86 | name: "Card 1", 87 | qfmt: "{{Simplified}}\n\n{{Pinyin}}", 88 | afmt: "{{FrontSide}}\n\n
\n\n{{Traditional}}\n\n{{Meaning}}", 89 | } 90 | ], 91 | }) 92 | 93 | const d = new Deck(1347617346765, "Chinese New Words") 94 | 95 | d.addNote(m.note(["如", "如", "rú", "according to, in accordance with, such as, as if, like, for example"])) 96 | d.addNote(m.note(["比如", "譬如", "pìrú", "for example, such as"])) 97 | 98 | const p = new Package() 99 | p.addDeck(d) 100 | p.writeToFile('chinese-deck.apkg') 101 | } 102 | 103 | /* 104 | Test add image file 105 | */ 106 | 107 | async function testImage() { 108 | const m = new Model({ 109 | name: "Basic Test", 110 | id: "3457826374725", 111 | flds: [ 112 | { name: "Front" }, 113 | { name: "Back" } 114 | ], 115 | req: [ 116 | [0, "all", [0]], 117 | ], 118 | tmpls: [ 119 | { 120 | name: "Card 1", 121 | qfmt: "{{Front}}", 122 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 123 | } 124 | ], 125 | }) 126 | 127 | const d = new Deck(1347617346765, "hi") 128 | 129 | var imageFile = "favicon.ico"; 130 | 131 | d.addNote(m.note(['This is front and back side contains image.', ''])) 132 | 133 | const p = new Package() 134 | p.addDeck(d) 135 | 136 | let blob = await fetch('favicon.ico').then(response => { 137 | if (!response.ok) { 138 | return null; 139 | } 140 | return response.blob(); 141 | }); 142 | 143 | p.addMedia(blob, imageFile); 144 | p.writeToFile('deck.apkg') 145 | } 146 | 147 | /* 148 | Test add tags to note data 149 | */ 150 | function test4() { 151 | var m = new Model({ 152 | name: "Basic (and reversed card) with tags", 153 | id: "1543634829848", 154 | flds: [ 155 | { name: "Front" }, 156 | { name: "Back" } 157 | ], 158 | req: [ 159 | [ 0, "all", [ 0 ] ], 160 | [ 1, "all", [ 1 ] ] 161 | ], 162 | tmpls: [ 163 | { 164 | name: "Card 1", 165 | qfmt: "{{Front}}", 166 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 167 | }, 168 | { 169 | name: "Card 2", 170 | qfmt: "{{Back}}", 171 | afmt: "{{FrontSide}}\n\n
\n\n{{Front}}", 172 | } 173 | ], 174 | }) 175 | 176 | var d = new Deck(1276438724687, "Test Deck with Tags") 177 | 178 | d.addNote(m.note(['this is front', 'this is back'], ['test_tag1', 'test_tag2'])) 179 | 180 | var p = new Package() 181 | p.addDeck(d) 182 | 183 | p.writeToFile('deck.apkg') 184 | } -------------------------------------------------------------------------------- /docs/documentation.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | *Following documentation taken from [mkanki](https://github.com/nornagon/mkanki)* 4 | 5 | First, some information about how Anki works. 6 | 7 | In Anki, things to be remembered are called _notes_. Each note can have several 8 | fields—most commonly, a "Front" and a "Back", but the fields can be arbitrary. 9 | Each note can potentially correspond to many individual _cards_, each of which 10 | should help you remember one facet of the note. 11 | 12 | The thing that describes how those fields are turned into flashcards is called 13 | a _model_. Every note is described by exactly one model. The model defines 14 | which fields are allowed, and additionally defines one or more _templates_, 15 | which are written as HTML with [mustache](https://mustache.github.io/)-like 16 | placeholders. 17 | 18 | Finally, each card belongs to a _deck_. Decks collect cards into logical groups 19 | that you might want to study separately from each other. 20 | 21 | _Models_, _notes_, _cards_ and _decks_ are the fundamental concepts of Anki. In 22 | mkanki, cards are implicitly defined by notes and models, and you will only 23 | deal with models, notes, and decks. 24 | 25 | ## 26 | ### `Model` 27 | 28 | Anki supports two types of models: _standard_ and _cloze_. Standard models 29 | generate one card per template, while cloze models generate one card per cloze 30 | deletion. See the [Anki cloze documentation][anki-cloze-docs] for more on cloze 31 | deletion. 32 | 33 | #### `new anki.Model(props)` 34 | 35 | Create a new standard model with the given properties. `props` is an object 36 | with the following fields. 37 | 38 | - `id` string - a stable, unique identifier for this model. Generate this once 39 | with `+new Date` and then hard-code it into your code. Keeping this stable 40 | means that if the package is updated and re-imported into Anki, the app will 41 | be able to tell which cards are new and which cards should be merged into 42 | already-existing cards, preserving study history. 43 | - `name` string - the name of the model. Shows up in the "Add" UI in Anki. 44 | - `flds` Array<{name: string}> - the fields in the model. 45 | - `tmpls` Array<{name?: string, qfmt: string, afmt: string}> - a list of 46 | card templates to be generated from each note. `qfmt` is the HTML template 47 | for the question, and `afmt` is the HTML template for the answer. `name` is 48 | displayed in the configuration screen in Anki and nowhere else, and will 49 | default to "Card N". See the [Anki template 50 | documentation][anki-template-docs] for more on template formatting. 51 | - `req` Array<[number, "all" | "any", Array<number>]> - this 52 | describes which fields must be non-empty in order for a card to be generated. 53 | Each entry in this list is a tuple of the template index, "all" or "any", and 54 | a list of field indices. In order for a card to be generated for a given note 55 | and template, one or all of the fields specified in the field list must be 56 | non-empty. If the requirement isn't met for a given (template, note) pair, no 57 | card will be generated. 58 | 59 | #### `new anki.ClozeModel(props)` 60 | 61 | Create a new cloze model with the given properties. `props` is an object with 62 | the following fields. 63 | 64 | - `id` string - a stable, unique identifier for this model. Generate this once 65 | with `+new Date` and then hard-code it into your code. Keeping this stable 66 | means that if the package is updated and re-imported into Anki, the app will 67 | be able to tell which cards are new and which cards should be merged into 68 | already-existing cards, preserving study history. 69 | - `name` string - the name of the model. Shows up in the "Add" UI in Anki. 70 | - `flds` Array<{name: string}> - the fields in the model. 71 | - `tmpl` {name?: string, qfmt: string, afmt: string} - the cloze template to 72 | be generated from each note. `qfmt` is the HTML template for the question, 73 | and `afmt` is the HTML template for the answer. `name` is displayed in the 74 | configuration screen in Anki and nowhere else, and will default to "Cloze". 75 | See the [Anki template documentation][anki-cloze-template-docs] for more on 76 | cloze template formatting. Cloze models can only have one template. 77 | 78 | #### `model.note(fieldValues, [tags], [guid])` 79 | 80 | Create a note using this model. 81 | 82 | - `fieldValues` Array<string> | {[fieldName: string]: string} - If 83 | `fieldValues` is an array, the order of fields will be matched with the order 84 | of the `flds` in the model. If `fieldValues` is an object, the keys must be 85 | the names of fields in the model. 86 | - `tags` is an array, the array will be joined with space separated values in `Package` 87 | - `guid` string _(optional)_ - a stable, unique identifier for this note. When 88 | re-importing an updated version of this note, Anki will replace notes with 89 | matching identifiers. Defaults to a hash of the field values. 90 | - If you want to add custom `guid` without `tags`: put 2nd parameter as `null` or blank array `[]` 91 | Example: `model.note(["front value", "back value"], null, guid)` 92 | 93 | ### `Deck` 94 | 95 | In mkanki, decks are collections of notes (not cards, as in Anki proper). 96 | 97 | #### `new anki.Deck(id, name)` 98 | 99 | Create a new deck. 100 | 101 | - `id` string - a stable, unique identifier for this deck. Generate this once 102 | with `+new Date` and then hard-code it into your code. Keeping this stable 103 | means that if the package is updated and re-imported into Anki, the app will 104 | be able to tell which cards are new and which cards should be merged into 105 | already-existing cards, preserving study history. 106 | - `name` string - the name of the deck. When importing, Anki will create new 107 | decks with the specified names for each deck in the package. 108 | 109 | #### `deck.addNote(note)` 110 | 111 | Add a note to this deck. Technically, it is possible for a single note in Anki 112 | to generate cards belonging to multiple decks, but mkanki does not support 113 | that. 114 | 115 | - `note` Note - create notes using [`model.note()`](#modelnotefieldvalues). 116 | 117 | ### `Package` 118 | 119 | A package collects together decks, notes, and any media objects (images, audio, 120 | video, etc.) to be exported into a `.apkg` file. 121 | 122 | #### `new anki.Package()` 123 | 124 | Create a new empty package. 125 | 126 | #### `package.addDeck(deck)` 127 | 128 | Add a deck to this package. 129 | 130 | - `deck` [Deck](#deck) - the deck to add. 131 | 132 | #### `package.addMedia(data, name)` 133 | 134 | Add a media file to this package. 135 | 136 | - `data` string | Buffer - the contents of the media file. 137 | - `name` string - the name of the file in the package. 138 | 139 | #### `package.addMediaFile(filename, [name])` 140 | 141 | Add a media file from the filesystem to this package. 142 | 143 | - `filename` string - path to the file. 144 | - `name` string _(optional)_ - the name of the file in the package. Defaults to 145 | `filename`. 146 | 147 | #### `package.writeToFile(filename)` 148 | 149 | Serializes the package to a file. 150 | 151 | - `filename` string - path to the exported package. Conventionally ends in 152 | `".apkg"`. 153 | 154 | 155 | [anki-template-docs]: https://apps.ankiweb.net/docs/manual.html#cards-and-templates 156 | [anki-cloze-docs]: https://apps.ankiweb.net/docs/manual.html#cloze-deletion 157 | [anki-cloze-template-docs]: https://apps.ankiweb.net/docs/manual.html#cloze-templates 158 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Basic 4 | ### Basic and reversed card 5 | 6 | ```js 7 | var m = new Model({ 8 | name: "Basic (and reversed card)", 9 | id: "1543634829843", 10 | flds: [ 11 | { name: "Front" }, 12 | { name: "Back" } 13 | ], 14 | req: [ 15 | [ 0, "all", [ 0 ] ], 16 | [ 1, "all", [ 1 ] ] 17 | ], 18 | tmpls: [ 19 | { 20 | name: "Card 1", 21 | qfmt: "{{Front}}", 22 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 23 | }, 24 | { 25 | name: "Card 2", 26 | qfmt: "{{Back}}", 27 | afmt: "{{FrontSide}}\n\n
\n\n{{Front}}", 28 | } 29 | ], 30 | }) 31 | 32 | var d = new Deck(1276438724672, "Test Deck") 33 | 34 | d.addNote(m.note(['this is front', 'this is back'])) 35 | 36 | var p = new Package() 37 | p.addDeck(d) 38 | 39 | p.writeToFile('deck.apkg') 40 | 41 | ``` 42 | 43 | ### Two notes 44 | 45 | ```js 46 | function twoNotes() { 47 | const m = new Model({ 48 | name: "Basic", 49 | id: "1542906796044", 50 | flds: [ 51 | { name: "Front" }, 52 | { name: "Back" } 53 | ], 54 | req: [ 55 | [0, "all", [0]], 56 | ], 57 | tmpls: [ 58 | { 59 | name: "Card 1", 60 | qfmt: "{{Front}}", 61 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 62 | } 63 | ], 64 | }) 65 | 66 | const d = new Deck(1347617346765, "hi") 67 | 68 | d.addNote(m.note(['hello', 'world'])) 69 | d.addNote(m.note(['this is test', 'for anki'])) 70 | 71 | const p = new Package() 72 | p.addDeck(d) 73 | p.writeToFile('deck.apkg') 74 | } 75 | ``` 76 | 77 | ### Ten notes in loop 78 | 79 | ```js 80 | function exampleNotes10() { 81 | const m = new Model({ 82 | name: "Basic", 83 | id: "1542906796044", 84 | flds: [ 85 | { name: "Front" }, 86 | { name: "Back" } 87 | ], 88 | req: [ 89 | [0, "all", [0]], 90 | ], 91 | tmpls: [ 92 | { 93 | name: "Card 1", 94 | qfmt: "{{Front}}", 95 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 96 | } 97 | ], 98 | }) 99 | 100 | const d = new Deck(1347617346765, "hi") 101 | 102 | for (i = 0; i < 10; i++) { 103 | d.addNote(m.note(['front note ' + i, 'back note ' + i])) 104 | } 105 | 106 | const p = new Package() 107 | p.addDeck(d) 108 | p.writeToFile('deck.apkg') 109 | } 110 | 111 | ``` 112 | 113 | ### Two Chinese notes 114 | 115 | ```js 116 | function chineseNotes() { 117 | const m = new Model({ 118 | name: "Basic", 119 | id: "1542906796045", 120 | flds: [ 121 | { name: "Simplified" }, 122 | { name: "Traditional" }, 123 | { name: "Pinyin" }, 124 | { name: "Meaning" }, 125 | ], 126 | req: [ 127 | [0, "all", [0]], 128 | ], 129 | tmpls: [ 130 | { 131 | name: "Card 1", 132 | qfmt: "{{Simplified}}\n\n{{Pinyin}}", 133 | afmt: "{{FrontSide}}\n\n
\n\n{{Traditional}}\n\n{{Meaning}}", 134 | } 135 | ], 136 | }) 137 | 138 | const d = new Deck(1347617346765, "Chinese New Words") 139 | 140 | d.addNote(m.note(["如", "如", "rú", "according to, in accordance with, such as, as if, like, for example"])) 141 | d.addNote(m.note(["比如", "譬如", "pìrú", "for example, such as"])) 142 | 143 | const p = new Package() 144 | p.addDeck(d) 145 | p.writeToFile('chinese-deck.apkg') 146 | } 147 | ``` 148 | 149 | ### Add image file 150 | 151 | ```js 152 | async function addImage() { 153 | const m = new Model({ 154 | name: "Basic Test", 155 | id: "3457826374725", 156 | flds: [ 157 | { name: "Front" }, 158 | { name: "Back" } 159 | ], 160 | req: [ 161 | [0, "all", [0]], 162 | ], 163 | tmpls: [ 164 | { 165 | name: "Card 1", 166 | qfmt: "{{Front}}", 167 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 168 | } 169 | ], 170 | }) 171 | 172 | const d = new Deck(1347617346765, "hi") 173 | 174 | var imageFile = "favicon.ico"; 175 | 176 | d.addNote(m.note(['This is front and back side contains image.', ''])) 177 | 178 | const p = new Package() 179 | p.addDeck(d) 180 | 181 | let blob = await fetch('favicon.ico').then(response => { 182 | if (!response.ok) { 183 | return null; 184 | } 185 | return response.blob(); 186 | }); 187 | 188 | p.addMedia(blob, imageFile); 189 | p.writeToFile('deck.apkg') 190 | } 191 | 192 | ``` 193 | 194 | ### Add tags to notes 195 | ```js 196 | var m = new Model({ 197 | name: "Basic (and reversed card) with tags", 198 | id: "1543634829848", 199 | flds: [ 200 | { name: "Front" }, 201 | { name: "Back" } 202 | ], 203 | req: [ 204 | [ 0, "all", [ 0 ] ], 205 | [ 1, "all", [ 1 ] ] 206 | ], 207 | tmpls: [ 208 | { 209 | name: "Card 1", 210 | qfmt: "{{Front}}", 211 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 212 | }, 213 | { 214 | name: "Card 2", 215 | qfmt: "{{Back}}", 216 | afmt: "{{FrontSide}}\n\n
\n\n{{Front}}", 217 | } 218 | ], 219 | }) 220 | 221 | var d = new Deck(1276438724687, "Test Deck with Tags") 222 | 223 | d.addNote(m.note(['this is front', 'this is back'], ['test_tag1', 'test_tag2'])) 224 | 225 | var p = new Package() 226 | p.addDeck(d) 227 | 228 | p.writeToFile('deck.apkg') 229 | 230 | ``` -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | genanki-js 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /genanki/anki_hash.js: -------------------------------------------------------------------------------- 1 | BASE91_TABLE = [ 2 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 3 | 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 4 | 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', 5 | '5', '6', '7', '8', '9', '!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '-', '.', '/', ':', 6 | ';', '<', '=', '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~'] 7 | 8 | function ankiHash(fields) { 9 | const str = fields.join('__') 10 | const h = sha256.create(); 11 | h.update(str) 12 | const hex = h.digest() 13 | 14 | let hash_int = 0n 15 | for (let i = 0; i < 8; i++) { 16 | hash_int *= 256n 17 | hash_int += BigInt(hex[i]) 18 | } 19 | 20 | // convert to the weird base91 format that Anki uses 21 | let rv_reversed = [] 22 | while (hash_int > 0) { 23 | rv_reversed.push(BASE91_TABLE[hash_int % 91n]) 24 | hash_int = (hash_int / 91n) 25 | } 26 | 27 | return rv_reversed.reverse().join('') 28 | } 29 | -------------------------------------------------------------------------------- /genanki/apkg_col.js: -------------------------------------------------------------------------------- 1 | var APKG_COL = ` 2 | INSERT INTO col VALUES( 3 | null, 4 | 1411124400, 5 | 1425279151694, 6 | 1425279151690, 7 | 11, 8 | 0, 9 | 0, 10 | 0, 11 | '{ 12 | "activeDecks": [ 13 | 1 14 | ], 15 | "addToCur": true, 16 | "collapseTime": 1200, 17 | "curDeck": 1, 18 | "curModel": "1425279151691", 19 | "dueCounts": true, 20 | "estTimes": true, 21 | "newBury": true, 22 | "newSpread": 0, 23 | "nextPos": 1, 24 | "sortBackwards": false, 25 | "sortType": "noteFld", 26 | "timeLim": 0 27 | }', 28 | '{}', 29 | '{ 30 | "1": { 31 | "collapsed": false, 32 | "conf": 1, 33 | "desc": "", 34 | "dyn": 0, 35 | "extendNew": 10, 36 | "extendRev": 50, 37 | "id": 1, 38 | "lrnToday": [ 39 | 0, 40 | 0 41 | ], 42 | "mod": 1425279151, 43 | "name": "Default", 44 | "newToday": [ 45 | 0, 46 | 0 47 | ], 48 | "revToday": [ 49 | 0, 50 | 0 51 | ], 52 | "timeToday": [ 53 | 0, 54 | 0 55 | ], 56 | "usn": 0 57 | } 58 | }', 59 | '{ 60 | "1": { 61 | "autoplay": true, 62 | "id": 1, 63 | "lapse": { 64 | "delays": [ 65 | 10 66 | ], 67 | "leechAction": 0, 68 | "leechFails": 8, 69 | "minInt": 1, 70 | "mult": 0 71 | }, 72 | "maxTaken": 60, 73 | "mod": 0, 74 | "name": "Default", 75 | "new": { 76 | "bury": true, 77 | "delays": [ 78 | 1, 79 | 10 80 | ], 81 | "initialFactor": 2500, 82 | "ints": [ 83 | 1, 84 | 4, 85 | 7 86 | ], 87 | "order": 1, 88 | "perDay": 20, 89 | "separate": true 90 | }, 91 | "replayq": true, 92 | "rev": { 93 | "bury": true, 94 | "ease4": 1.3, 95 | "fuzz": 0.05, 96 | "ivlFct": 1, 97 | "maxIvl": 36500, 98 | "minSpace": 1, 99 | "perDay": 100 100 | }, 101 | "timer": 0, 102 | "usn": 0 103 | } 104 | }', 105 | '{}' 106 | ); 107 | `; 108 | -------------------------------------------------------------------------------- /genanki/apkg_schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | * [genanki]{@link https://github.com/kerrickstaley/genanki} 3 | * @copyright Copyright (c) Kerrick Staley 2021 4 | * @license The MIT License 5 | */ 6 | 7 | const APKG_SCHEMA = ` 8 | PRAGMA foreign_keys=OFF; 9 | BEGIN TRANSACTION; 10 | 11 | CREATE TABLE col ( 12 | id integer primary key, 13 | crt integer not null, 14 | mod integer not null, 15 | scm integer not null, 16 | ver integer not null, 17 | dty integer not null, 18 | usn integer not null, 19 | ls integer not null, 20 | conf text not null, 21 | models text not null, 22 | decks text not null, 23 | dconf text not null, 24 | tags text not null 25 | ); 26 | CREATE TABLE notes ( 27 | id integer primary key, /* 0 */ 28 | guid text not null, /* 1 */ 29 | mid integer not null, /* 2 */ 30 | mod integer not null, /* 3 */ 31 | usn integer not null, /* 4 */ 32 | tags text not null, /* 5 */ 33 | flds text not null, /* 6 */ 34 | sfld integer not null, /* 7 */ 35 | csum integer not null, /* 8 */ 36 | flags integer not null, /* 9 */ 37 | data text not null /* 10 */ 38 | ); 39 | CREATE TABLE cards ( 40 | id integer primary key, /* 0 */ 41 | nid integer not null, /* 1 */ 42 | did integer not null, /* 2 */ 43 | ord integer not null, /* 3 */ 44 | mod integer not null, /* 4 */ 45 | usn integer not null, /* 5 */ 46 | type integer not null, /* 6 */ 47 | queue integer not null, /* 7 */ 48 | due integer not null, /* 8 */ 49 | ivl integer not null, /* 9 */ 50 | factor integer not null, /* 10 */ 51 | reps integer not null, /* 11 */ 52 | lapses integer not null, /* 12 */ 53 | left integer not null, /* 13 */ 54 | odue integer not null, /* 14 */ 55 | odid integer not null, /* 15 */ 56 | flags integer not null, /* 16 */ 57 | data text not null /* 17 */ 58 | ); 59 | CREATE TABLE revlog ( 60 | id integer primary key, 61 | cid integer not null, 62 | usn integer not null, 63 | ease integer not null, 64 | ivl integer not null, 65 | lastIvl integer not null, 66 | factor integer not null, 67 | time integer not null, 68 | type integer not null 69 | ); 70 | CREATE TABLE graves ( 71 | usn integer not null, 72 | oid integer not null, 73 | type integer not null 74 | ); 75 | CREATE INDEX ix_notes_usn on notes (usn); 76 | CREATE INDEX ix_cards_usn on cards (usn); 77 | CREATE INDEX ix_revlog_usn on revlog (usn); 78 | CREATE INDEX ix_cards_nid on cards (nid); 79 | CREATE INDEX ix_cards_sched on cards (did, queue, due); 80 | CREATE INDEX ix_revlog_cid on revlog (cid); 81 | CREATE INDEX ix_notes_csum on notes (csum); 82 | COMMIT; 83 | `; 84 | -------------------------------------------------------------------------------- /genanki/deck.js: -------------------------------------------------------------------------------- 1 | class Deck { 2 | constructor(id, name, desc="") { 3 | this.id = id 4 | this.name = name 5 | this.desc = desc 6 | this.notes = [] 7 | } 8 | 9 | addNote(note) { 10 | this.notes.push(note) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /genanki/default.js: -------------------------------------------------------------------------------- 1 | const defaultModel = { 2 | sortf: 0, // sort field 3 | did: 1, // deck id 4 | latexPre: `\\documentclass[12pt]{article} 5 | \\special{papersize=3in,5in} 6 | \\usepackage[utf8]{inputenc} 7 | \\usepackage{amssymb,amsmath} 8 | \\pagestyle{empty} 9 | \\setlength{\\parindent}{0in} 10 | \\begin{document}`, 11 | latexPost: "\\end{document}", 12 | mod: 0, // modification time 13 | usn: 0, // unsure, something to do with sync? 14 | vers: [], // seems to be unused 15 | type: MODEL_STD, 16 | css: `.card { 17 | font-family: arial; 18 | font-size: 20px; 19 | text-align: center; 20 | color: black; 21 | background-color: white; 22 | }`, 23 | /* also: 24 | name: string, 25 | flds: [Field], 26 | tmpls: [Template], 27 | tags: [??], 28 | id: string 29 | */ 30 | tags: [], 31 | } 32 | 33 | const defaultField = { 34 | name: "", 35 | ord: null, 36 | sticky: false, 37 | rtl: false, 38 | font: "Arial", 39 | size: 20, 40 | media: [], 41 | } 42 | 43 | const defaultTemplate = { 44 | name: "", 45 | ord: null, 46 | qfmt: "", 47 | afmt: "", 48 | did: null, 49 | bqfmt: "", 50 | bafmt: "", 51 | } 52 | 53 | // whether new cards should be mixed with reviews, or shown first or last 54 | const NEW_CARDS_DISTRIBUTE = 0 55 | const NEW_CARDS_LAST = 1 56 | const NEW_CARDS_FIRST = 2 57 | 58 | const defaultConf = { 59 | // review options 60 | 'activeDecks': [1], 61 | 'curDeck': 1, 62 | 'newSpread': NEW_CARDS_DISTRIBUTE, 63 | 'collapseTime': 1200, 64 | 'timeLim': 0, 65 | 'estTimes': true, 66 | 'dueCounts': true, 67 | // other config 68 | 'curModel': null, 69 | 'nextPos': 1, 70 | 'sortType': "noteFld", 71 | 'sortBackwards': false, 72 | 'addToCur': true, // add new to currently selected deck? 73 | 'dayLearnFirst': false, 74 | } 75 | 76 | 77 | // new card insertion order 78 | const NEW_CARDS_RANDOM = 0 79 | const NEW_CARDS_DUE = 1 80 | 81 | const STARTING_FACTOR = 2500 82 | 83 | const defaultDeckConf = { 84 | 'name': "Default", 85 | 'new': { 86 | 'delays': [1, 10], 87 | 'ints': [1, 4, 7], // 7 is not currently used 88 | 'initialFactor': STARTING_FACTOR, 89 | 'separate': true, 90 | 'order': NEW_CARDS_DUE, 91 | 'perDay': 20, 92 | // may not be set on old decks 93 | 'bury': false, 94 | }, 95 | 'lapse': { 96 | 'delays': [10], 97 | 'mult': 0, 98 | 'minInt': 1, 99 | 'leechFails': 8, 100 | // type 0=suspend, 1=tagonly 101 | 'leechAction': 0, 102 | }, 103 | 'rev': { 104 | 'perDay': 200, 105 | 'ease4': 1.3, 106 | 'fuzz': 0.05, 107 | 'minSpace': 1, // not currently used 108 | 'ivlFct': 1, 109 | 'maxIvl': 36500, 110 | // may not be set on old decks 111 | 'bury': false, 112 | 'hardFactor': 1.2, 113 | }, 114 | 'maxTaken': 60, 115 | 'timer': 0, 116 | 'autoplay': true, 117 | 'replayq': true, 118 | 'mod': 0, 119 | 'usn': 0, 120 | } 121 | 122 | const defaultDeck = { 123 | newToday: [0, 0], // currentDay, count 124 | revToday: [0, 0], 125 | lrnToday: [0, 0], 126 | timeToday: [0, 0], // time in ms 127 | conf: 1, 128 | usn: 0, 129 | desc: "", 130 | dyn: 0, // anki uses int/bool interchangably here 131 | collapsed: false, 132 | // added in beta11 133 | extendNew: 10, 134 | extendRev: 50, 135 | } 136 | -------------------------------------------------------------------------------- /genanki/license.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [mkanki]{@link https://github.com/nornagon/mkanki} 3 | * @copyright Copyright (c) 2018 Jeremy Apthorp 4 | * @license AGPL-3.0 License 5 | * 6 | * 7 | * [genanki]{@link https://github.com/kerrickstaley/genanki} 8 | * @copyright Copyright (c) Kerrick Staley 2021 9 | * @license The MIT License 10 | * 11 | * 12 | * [genanki-js]{@link https://github.com/krmanik/genanki-js} 13 | * @copyright Copyright (c) 2021 Mani 14 | * @license AGPL-3.0 License 15 | */ 16 | -------------------------------------------------------------------------------- /genanki/model.js: -------------------------------------------------------------------------------- 1 | const MODEL_STD = 0 2 | const MODEL_CLOZE = 1 3 | 4 | class Model { 5 | constructor(props) { 6 | this.props = { 7 | ...defaultModel, 8 | ...props, 9 | flds: props.flds.map((f, i) => ({ ...defaultField, ord: i, ...f })), 10 | tmpls: props.tmpls.map((t, i) => ({ ...defaultTemplate, ord: i, name: `Card ${i + 1}`, ...t })), 11 | mod: new Date().getTime() 12 | } 13 | this.fieldNameToOrd = {} 14 | this.props.flds.forEach(f => { this.fieldNameToOrd[f.name] = f.ord }) 15 | } 16 | 17 | note(fields, tags, guid = null) { 18 | if (Array.isArray(fields)) { 19 | if (fields.length !== this.props.flds.length) { 20 | throw new Error(`Expected ${this.props.flds.length} fields for model '${this.props.name}' but got ${fields.length}`) 21 | } 22 | return new Note(this, fields, tags, guid) 23 | } else { 24 | const field_names = Object.keys(fields) 25 | const fields_list = [] 26 | field_names.forEach(field_name => { 27 | const ord = this.fieldNameToOrd[field_name] 28 | if (ord == null) throw new Error(`Field '${field_name}' does not exist in the model`) 29 | fields_list[ord] = fields[field_name] 30 | }) 31 | return new Note(this, fields_list, tags, guid) 32 | } 33 | } 34 | } 35 | 36 | class ClozeModel extends Model { 37 | constructor(props) { 38 | super({ 39 | type: MODEL_CLOZE, 40 | css: ` 41 | .card { 42 | font-family: arial; 43 | font-size: 20px; 44 | text-align: center; 45 | color: black; 46 | background-color: white; 47 | } 48 | 49 | .cloze { 50 | font-weight: bold; 51 | color: blue; 52 | } 53 | `, 54 | tmpls: [{ name: "Cloze", ...props.tmpl }], 55 | ...props 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /genanki/note.js: -------------------------------------------------------------------------------- 1 | class Note { 2 | constructor(model, fields, tags = null, guid = null) { 3 | this.model = model 4 | this.fields = fields 5 | this.tags = tags 6 | this._guid = guid 7 | } 8 | 9 | get guid() { 10 | return this._guid ? this._guid : ankiHash(this.fields); 11 | } 12 | 13 | get cards() { 14 | if (this.model.props.type === MODEL_STD) { 15 | const isEmpty = f => { 16 | return !f || f.toString().trim().length === 0 17 | } 18 | const rv = [] 19 | for (const [card_ord, any_or_all, required_field_ords] of this.model.props.req) { 20 | const op = any_or_all === "any" ? "some" : "every" 21 | if (required_field_ords[op](f => !isEmpty(this.fields[f]))) { 22 | rv.push(card_ord) 23 | } 24 | } 25 | return rv 26 | } else { 27 | // the below logic is copied from anki's ModelManager._availClozeOrds 28 | const ords = new Set() 29 | const matches = [] 30 | const curliesRe = /{{[^}]*?cloze:(?:[^}]?:)*(.+?)}}/g 31 | const percentRe = /<%cloze:(.+?)%>/g 32 | const { qfmt } = this.model.props.tmpls[0] // cloze models only have 1 template 33 | let m; 34 | while (m = curliesRe.exec(qfmt)) 35 | matches.push(m[1]) 36 | while (m = percentRe.exec(qfmt)) 37 | matches.push(m[1]) 38 | const map = {} 39 | this.model.props.flds.forEach((fld, i) => { 40 | map[fld.name] = [i, fld] 41 | }) 42 | for (const fname of matches) { 43 | if (!(fname in map)) continue 44 | const ord = map[fname][0] 45 | const re = /{{c(\d+)::.+?}}/gs 46 | while (m = re.exec(this.fields[ord])) { 47 | const i = parseInt(m[1]) 48 | if (i > 0) 49 | ords.add(i - 1) 50 | } 51 | } 52 | if (ords.size === 0) { 53 | // empty clozes use first ord 54 | return [0] 55 | } 56 | return Array.from(ords) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /genanki/package.js: -------------------------------------------------------------------------------- 1 | class Package { 2 | constructor() { 3 | this.decks = [] 4 | this.media = [] 5 | } 6 | 7 | addDeck(deck) { 8 | this.decks.push(deck) 9 | } 10 | 11 | addMedia(data, name) { 12 | this.media.push({ name, data }) 13 | } 14 | 15 | addMediaFile(filename, name = null) { 16 | this.media.push({ name: name || filename, filename }) 17 | } 18 | 19 | writeToFile(filename) { 20 | var db = new SQL.Database(); 21 | db.run(APKG_SCHEMA); 22 | 23 | this.write(db) 24 | 25 | var zip = new JSZip(); 26 | 27 | const data = db.export(); 28 | const buffer = new Uint8Array(data).buffer; 29 | 30 | zip.file("collection.anki2", buffer); 31 | 32 | const media_info = {} 33 | 34 | this.media.forEach((m, i) => { 35 | if (m.filename != null) { 36 | zip.file(i.toString(), m.filename) 37 | } else { 38 | zip.file(i.toString(), m.data) 39 | } 40 | 41 | media_info[i] = m.name 42 | }) 43 | 44 | zip.file('media', JSON.stringify(media_info)) 45 | 46 | zip.generateAsync({ type: "blob", mimeType: "application/apkg" }).then(function (content) { 47 | // see FileSaver.js 48 | saveAs(content, filename); 49 | }); 50 | } 51 | 52 | 53 | write(db) { 54 | const now = new Date 55 | const models = {} 56 | const decks = {} 57 | 58 | // AnkiDroid failed to import subdeck, So add a Default deck 59 | decks["1"] = {...defaultDeck, id: 1, name: "Default"} 60 | 61 | this.decks.forEach(d => { 62 | d.notes.forEach(n => models[n.model.props.id] = n.model.props) 63 | decks[d.id] = { 64 | ...defaultDeck, 65 | id: d.id, 66 | name: d.name, 67 | desc: d.desc, 68 | } 69 | }) 70 | 71 | const col = [ 72 | null, // id 73 | (+now / 1000) | 0, // crt 74 | +now, // mod 75 | +now, // scm 76 | 11, // ver 77 | 0, // dty 78 | 0, // usn 79 | 0, // ls 80 | JSON.stringify(defaultConf), // conf 81 | JSON.stringify(models), // models 82 | JSON.stringify(decks), // decks 83 | JSON.stringify({ 1: { id: 1, ...defaultDeckConf } }), // dconf 84 | JSON.stringify({}), // tags 85 | ] 86 | 87 | db.prepare(`INSERT INTO col 88 | (id, crt, mod, scm, ver, dty, usn, ls, conf, models, decks, dconf, tags) 89 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(col) 90 | 91 | const insert_notes = db.prepare(`INSERT INTO notes (id, guid, mid, mod, usn, tags, flds, sfld, csum, flags, data) 92 | VALUES (null, ?, ?, ?, ?, ?, ?, ?, 0, 0, '')`) 93 | 94 | const insert_cards = db.prepare(`INSERT INTO cards (id, nid, did, ord, mod, usn, type, queue, due, ivl, factor, reps, lapses, left, odue, odid, flags, data) 95 | VALUES (null, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, 0, 0, 0, 0, 0, 0, '')`) 96 | 97 | for (const deck of this.decks) { 98 | for (const note of deck.notes) { 99 | var tags = note.tags == null ? '' : note.tags.join(' ') 100 | insert_notes.run( 101 | [ 102 | note.guid, // guid 103 | note.model.props.id, // mid 104 | (+now / 1000) | 0, // mod 105 | -1, // usn 106 | tags, // tags 107 | note.fields.join('\x1f'), // flds 108 | 0, // sfld 109 | ]) 110 | 111 | var rowID = db.exec("select last_insert_rowid();") 112 | var note_id = rowID[0]['values'][0][0]; 113 | 114 | for (const card_ord of note.cards) { 115 | insert_cards.run( 116 | [ 117 | note_id, // nid 118 | deck.id, // did 119 | card_ord, // ord 120 | (+now / 1000) | 0, // mod 121 | -1, // usn 122 | 0, // type 0=new, 1=learning, 2=due 123 | 0, // queue -1 for suspended 124 | ]) 125 | } 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /genanki/sha256.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [js-sha256]{@link https://github.com/emn178/js-sha256} 3 | * 4 | * @version 0.9.0 5 | * @author Chen, Yi-Cyuan [emn178@gmail.com] 6 | * @copyright Chen, Yi-Cyuan 2014-2017 7 | * @license MIT 8 | */ 9 | /*jslint bitwise: true */ 10 | (function () { 11 | 'use strict'; 12 | 13 | var ERROR = 'input is invalid type'; 14 | var WINDOW = typeof window === 'object'; 15 | var root = WINDOW ? window : {}; 16 | if (root.JS_SHA256_NO_WINDOW) { 17 | WINDOW = false; 18 | } 19 | var WEB_WORKER = !WINDOW && typeof self === 'object'; 20 | var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; 21 | if (NODE_JS) { 22 | root = global; 23 | } else if (WEB_WORKER) { 24 | root = self; 25 | } 26 | var COMMON_JS = !root.JS_SHA256_NO_COMMON_JS && typeof module === 'object' && module.exports; 27 | var AMD = typeof define === 'function' && define.amd; 28 | var ARRAY_BUFFER = !root.JS_SHA256_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined'; 29 | var HEX_CHARS = '0123456789abcdef'.split(''); 30 | var EXTRA = [-2147483648, 8388608, 32768, 128]; 31 | var SHIFT = [24, 16, 8, 0]; 32 | var K = [ 33 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 34 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 35 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 36 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 37 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 38 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 39 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 40 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 41 | ]; 42 | var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer']; 43 | 44 | var blocks = []; 45 | 46 | if (root.JS_SHA256_NO_NODE_JS || !Array.isArray) { 47 | Array.isArray = function (obj) { 48 | return Object.prototype.toString.call(obj) === '[object Array]'; 49 | }; 50 | } 51 | 52 | if (ARRAY_BUFFER && (root.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) { 53 | ArrayBuffer.isView = function (obj) { 54 | return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer; 55 | }; 56 | } 57 | 58 | var createOutputMethod = function (outputType, is224) { 59 | return function (message) { 60 | return new Sha256(is224, true).update(message)[outputType](); 61 | }; 62 | }; 63 | 64 | var createMethod = function (is224) { 65 | var method = createOutputMethod('hex', is224); 66 | if (NODE_JS) { 67 | method = nodeWrap(method, is224); 68 | } 69 | method.create = function () { 70 | return new Sha256(is224); 71 | }; 72 | method.update = function (message) { 73 | return method.create().update(message); 74 | }; 75 | for (var i = 0; i < OUTPUT_TYPES.length; ++i) { 76 | var type = OUTPUT_TYPES[i]; 77 | method[type] = createOutputMethod(type, is224); 78 | } 79 | return method; 80 | }; 81 | 82 | var nodeWrap = function (method, is224) { 83 | var crypto = eval("require('crypto')"); 84 | var Buffer = eval("require('buffer').Buffer"); 85 | var algorithm = is224 ? 'sha224' : 'sha256'; 86 | var nodeMethod = function (message) { 87 | if (typeof message === 'string') { 88 | return crypto.createHash(algorithm).update(message, 'utf8').digest('hex'); 89 | } else { 90 | if (message === null || message === undefined) { 91 | throw new Error(ERROR); 92 | } else if (message.constructor === ArrayBuffer) { 93 | message = new Uint8Array(message); 94 | } 95 | } 96 | if (Array.isArray(message) || ArrayBuffer.isView(message) || 97 | message.constructor === Buffer) { 98 | return crypto.createHash(algorithm).update(new Buffer(message)).digest('hex'); 99 | } else { 100 | return method(message); 101 | } 102 | }; 103 | return nodeMethod; 104 | }; 105 | 106 | var createHmacOutputMethod = function (outputType, is224) { 107 | return function (key, message) { 108 | return new HmacSha256(key, is224, true).update(message)[outputType](); 109 | }; 110 | }; 111 | 112 | var createHmacMethod = function (is224) { 113 | var method = createHmacOutputMethod('hex', is224); 114 | method.create = function (key) { 115 | return new HmacSha256(key, is224); 116 | }; 117 | method.update = function (key, message) { 118 | return method.create(key).update(message); 119 | }; 120 | for (var i = 0; i < OUTPUT_TYPES.length; ++i) { 121 | var type = OUTPUT_TYPES[i]; 122 | method[type] = createHmacOutputMethod(type, is224); 123 | } 124 | return method; 125 | }; 126 | 127 | function Sha256(is224, sharedMemory) { 128 | if (sharedMemory) { 129 | blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = 130 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 131 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 132 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 133 | this.blocks = blocks; 134 | } else { 135 | this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 136 | } 137 | 138 | if (is224) { 139 | this.h0 = 0xc1059ed8; 140 | this.h1 = 0x367cd507; 141 | this.h2 = 0x3070dd17; 142 | this.h3 = 0xf70e5939; 143 | this.h4 = 0xffc00b31; 144 | this.h5 = 0x68581511; 145 | this.h6 = 0x64f98fa7; 146 | this.h7 = 0xbefa4fa4; 147 | } else { // 256 148 | this.h0 = 0x6a09e667; 149 | this.h1 = 0xbb67ae85; 150 | this.h2 = 0x3c6ef372; 151 | this.h3 = 0xa54ff53a; 152 | this.h4 = 0x510e527f; 153 | this.h5 = 0x9b05688c; 154 | this.h6 = 0x1f83d9ab; 155 | this.h7 = 0x5be0cd19; 156 | } 157 | 158 | this.block = this.start = this.bytes = this.hBytes = 0; 159 | this.finalized = this.hashed = false; 160 | this.first = true; 161 | this.is224 = is224; 162 | } 163 | 164 | Sha256.prototype.update = function (message) { 165 | if (this.finalized) { 166 | return; 167 | } 168 | var notString, type = typeof message; 169 | if (type !== 'string') { 170 | if (type === 'object') { 171 | if (message === null) { 172 | throw new Error(ERROR); 173 | } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) { 174 | message = new Uint8Array(message); 175 | } else if (!Array.isArray(message)) { 176 | if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) { 177 | throw new Error(ERROR); 178 | } 179 | } 180 | } else { 181 | throw new Error(ERROR); 182 | } 183 | notString = true; 184 | } 185 | var code, index = 0, i, length = message.length, blocks = this.blocks; 186 | 187 | while (index < length) { 188 | if (this.hashed) { 189 | this.hashed = false; 190 | blocks[0] = this.block; 191 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = 192 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 193 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 194 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 195 | } 196 | 197 | if (notString) { 198 | for (i = this.start; index < length && i < 64; ++index) { 199 | blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; 200 | } 201 | } else { 202 | for (i = this.start; index < length && i < 64; ++index) { 203 | code = message.charCodeAt(index); 204 | if (code < 0x80) { 205 | blocks[i >> 2] |= code << SHIFT[i++ & 3]; 206 | } else if (code < 0x800) { 207 | blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; 208 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 209 | } else if (code < 0xd800 || code >= 0xe000) { 210 | blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; 211 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 212 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 213 | } else { 214 | code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); 215 | blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; 216 | blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; 217 | blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; 218 | blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; 219 | } 220 | } 221 | } 222 | 223 | this.lastByteIndex = i; 224 | this.bytes += i - this.start; 225 | if (i >= 64) { 226 | this.block = blocks[16]; 227 | this.start = i - 64; 228 | this.hash(); 229 | this.hashed = true; 230 | } else { 231 | this.start = i; 232 | } 233 | } 234 | if (this.bytes > 4294967295) { 235 | this.hBytes += this.bytes / 4294967296 << 0; 236 | this.bytes = this.bytes % 4294967296; 237 | } 238 | return this; 239 | }; 240 | 241 | Sha256.prototype.finalize = function () { 242 | if (this.finalized) { 243 | return; 244 | } 245 | this.finalized = true; 246 | var blocks = this.blocks, i = this.lastByteIndex; 247 | blocks[16] = this.block; 248 | blocks[i >> 2] |= EXTRA[i & 3]; 249 | this.block = blocks[16]; 250 | if (i >= 56) { 251 | if (!this.hashed) { 252 | this.hash(); 253 | } 254 | blocks[0] = this.block; 255 | blocks[16] = blocks[1] = blocks[2] = blocks[3] = 256 | blocks[4] = blocks[5] = blocks[6] = blocks[7] = 257 | blocks[8] = blocks[9] = blocks[10] = blocks[11] = 258 | blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; 259 | } 260 | blocks[14] = this.hBytes << 3 | this.bytes >>> 29; 261 | blocks[15] = this.bytes << 3; 262 | this.hash(); 263 | }; 264 | 265 | Sha256.prototype.hash = function () { 266 | var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4, f = this.h5, g = this.h6, 267 | h = this.h7, blocks = this.blocks, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc; 268 | 269 | for (j = 16; j < 64; ++j) { 270 | // rightrotate 271 | t1 = blocks[j - 15]; 272 | s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); 273 | t1 = blocks[j - 2]; 274 | s1 = ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10); 275 | blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0; 276 | } 277 | 278 | bc = b & c; 279 | for (j = 0; j < 64; j += 4) { 280 | if (this.first) { 281 | if (this.is224) { 282 | ab = 300032; 283 | t1 = blocks[0] - 1413257819; 284 | h = t1 - 150054599 << 0; 285 | d = t1 + 24177077 << 0; 286 | } else { 287 | ab = 704751109; 288 | t1 = blocks[0] - 210244248; 289 | h = t1 - 1521486534 << 0; 290 | d = t1 + 143694565 << 0; 291 | } 292 | this.first = false; 293 | } else { 294 | s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)); 295 | s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)); 296 | ab = a & b; 297 | maj = ab ^ (a & c) ^ bc; 298 | ch = (e & f) ^ (~e & g); 299 | t1 = h + s1 + ch + K[j] + blocks[j]; 300 | t2 = s0 + maj; 301 | h = d + t1 << 0; 302 | d = t1 + t2 << 0; 303 | } 304 | s0 = ((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10)); 305 | s1 = ((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7)); 306 | da = d & a; 307 | maj = da ^ (d & b) ^ ab; 308 | ch = (h & e) ^ (~h & f); 309 | t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; 310 | t2 = s0 + maj; 311 | g = c + t1 << 0; 312 | c = t1 + t2 << 0; 313 | s0 = ((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10)); 314 | s1 = ((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7)); 315 | cd = c & d; 316 | maj = cd ^ (c & a) ^ da; 317 | ch = (g & h) ^ (~g & e); 318 | t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; 319 | t2 = s0 + maj; 320 | f = b + t1 << 0; 321 | b = t1 + t2 << 0; 322 | s0 = ((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10)); 323 | s1 = ((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7)); 324 | bc = b & c; 325 | maj = bc ^ (b & d) ^ cd; 326 | ch = (f & g) ^ (~f & h); 327 | t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; 328 | t2 = s0 + maj; 329 | e = a + t1 << 0; 330 | a = t1 + t2 << 0; 331 | } 332 | 333 | this.h0 = this.h0 + a << 0; 334 | this.h1 = this.h1 + b << 0; 335 | this.h2 = this.h2 + c << 0; 336 | this.h3 = this.h3 + d << 0; 337 | this.h4 = this.h4 + e << 0; 338 | this.h5 = this.h5 + f << 0; 339 | this.h6 = this.h6 + g << 0; 340 | this.h7 = this.h7 + h << 0; 341 | }; 342 | 343 | Sha256.prototype.hex = function () { 344 | this.finalize(); 345 | 346 | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, 347 | h6 = this.h6, h7 = this.h7; 348 | 349 | var hex = HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] + 350 | HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + 351 | HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + 352 | HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + 353 | HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + 354 | HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + 355 | HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + 356 | HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + 357 | HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + 358 | HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + 359 | HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + 360 | HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + 361 | HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] + 362 | HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + 363 | HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + 364 | HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + 365 | HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] + 366 | HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] + 367 | HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] + 368 | HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F] + 369 | HEX_CHARS[(h5 >> 28) & 0x0F] + HEX_CHARS[(h5 >> 24) & 0x0F] + 370 | HEX_CHARS[(h5 >> 20) & 0x0F] + HEX_CHARS[(h5 >> 16) & 0x0F] + 371 | HEX_CHARS[(h5 >> 12) & 0x0F] + HEX_CHARS[(h5 >> 8) & 0x0F] + 372 | HEX_CHARS[(h5 >> 4) & 0x0F] + HEX_CHARS[h5 & 0x0F] + 373 | HEX_CHARS[(h6 >> 28) & 0x0F] + HEX_CHARS[(h6 >> 24) & 0x0F] + 374 | HEX_CHARS[(h6 >> 20) & 0x0F] + HEX_CHARS[(h6 >> 16) & 0x0F] + 375 | HEX_CHARS[(h6 >> 12) & 0x0F] + HEX_CHARS[(h6 >> 8) & 0x0F] + 376 | HEX_CHARS[(h6 >> 4) & 0x0F] + HEX_CHARS[h6 & 0x0F]; 377 | if (!this.is224) { 378 | hex += HEX_CHARS[(h7 >> 28) & 0x0F] + HEX_CHARS[(h7 >> 24) & 0x0F] + 379 | HEX_CHARS[(h7 >> 20) & 0x0F] + HEX_CHARS[(h7 >> 16) & 0x0F] + 380 | HEX_CHARS[(h7 >> 12) & 0x0F] + HEX_CHARS[(h7 >> 8) & 0x0F] + 381 | HEX_CHARS[(h7 >> 4) & 0x0F] + HEX_CHARS[h7 & 0x0F]; 382 | } 383 | return hex; 384 | }; 385 | 386 | Sha256.prototype.toString = Sha256.prototype.hex; 387 | 388 | Sha256.prototype.digest = function () { 389 | this.finalize(); 390 | 391 | var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, 392 | h6 = this.h6, h7 = this.h7; 393 | 394 | var arr = [ 395 | (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF, 396 | (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF, 397 | (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF, 398 | (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF, 399 | (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF, 400 | (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, h5 & 0xFF, 401 | (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, h6 & 0xFF 402 | ]; 403 | if (!this.is224) { 404 | arr.push((h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, h7 & 0xFF); 405 | } 406 | return arr; 407 | }; 408 | 409 | Sha256.prototype.array = Sha256.prototype.digest; 410 | 411 | Sha256.prototype.arrayBuffer = function () { 412 | this.finalize(); 413 | 414 | var buffer = new ArrayBuffer(this.is224 ? 28 : 32); 415 | var dataView = new DataView(buffer); 416 | dataView.setUint32(0, this.h0); 417 | dataView.setUint32(4, this.h1); 418 | dataView.setUint32(8, this.h2); 419 | dataView.setUint32(12, this.h3); 420 | dataView.setUint32(16, this.h4); 421 | dataView.setUint32(20, this.h5); 422 | dataView.setUint32(24, this.h6); 423 | if (!this.is224) { 424 | dataView.setUint32(28, this.h7); 425 | } 426 | return buffer; 427 | }; 428 | 429 | function HmacSha256(key, is224, sharedMemory) { 430 | var i, type = typeof key; 431 | if (type === 'string') { 432 | var bytes = [], length = key.length, index = 0, code; 433 | for (i = 0; i < length; ++i) { 434 | code = key.charCodeAt(i); 435 | if (code < 0x80) { 436 | bytes[index++] = code; 437 | } else if (code < 0x800) { 438 | bytes[index++] = (0xc0 | (code >> 6)); 439 | bytes[index++] = (0x80 | (code & 0x3f)); 440 | } else if (code < 0xd800 || code >= 0xe000) { 441 | bytes[index++] = (0xe0 | (code >> 12)); 442 | bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); 443 | bytes[index++] = (0x80 | (code & 0x3f)); 444 | } else { 445 | code = 0x10000 + (((code & 0x3ff) << 10) | (key.charCodeAt(++i) & 0x3ff)); 446 | bytes[index++] = (0xf0 | (code >> 18)); 447 | bytes[index++] = (0x80 | ((code >> 12) & 0x3f)); 448 | bytes[index++] = (0x80 | ((code >> 6) & 0x3f)); 449 | bytes[index++] = (0x80 | (code & 0x3f)); 450 | } 451 | } 452 | key = bytes; 453 | } else { 454 | if (type === 'object') { 455 | if (key === null) { 456 | throw new Error(ERROR); 457 | } else if (ARRAY_BUFFER && key.constructor === ArrayBuffer) { 458 | key = new Uint8Array(key); 459 | } else if (!Array.isArray(key)) { 460 | if (!ARRAY_BUFFER || !ArrayBuffer.isView(key)) { 461 | throw new Error(ERROR); 462 | } 463 | } 464 | } else { 465 | throw new Error(ERROR); 466 | } 467 | } 468 | 469 | if (key.length > 64) { 470 | key = (new Sha256(is224, true)).update(key).array(); 471 | } 472 | 473 | var oKeyPad = [], iKeyPad = []; 474 | for (i = 0; i < 64; ++i) { 475 | var b = key[i] || 0; 476 | oKeyPad[i] = 0x5c ^ b; 477 | iKeyPad[i] = 0x36 ^ b; 478 | } 479 | 480 | Sha256.call(this, is224, sharedMemory); 481 | 482 | this.update(iKeyPad); 483 | this.oKeyPad = oKeyPad; 484 | this.inner = true; 485 | this.sharedMemory = sharedMemory; 486 | } 487 | HmacSha256.prototype = new Sha256(); 488 | 489 | HmacSha256.prototype.finalize = function () { 490 | Sha256.prototype.finalize.call(this); 491 | if (this.inner) { 492 | this.inner = false; 493 | var innerHash = this.array(); 494 | Sha256.call(this, this.is224, this.sharedMemory); 495 | this.update(this.oKeyPad); 496 | this.update(innerHash); 497 | Sha256.prototype.finalize.call(this); 498 | } 499 | }; 500 | 501 | var exports = createMethod(); 502 | exports.sha256 = exports; 503 | exports.sha224 = createMethod(true); 504 | exports.sha256.hmac = createHmacMethod(); 505 | exports.sha224.hmac = createHmacMethod(true); 506 | 507 | if (COMMON_JS) { 508 | module.exports = exports; 509 | } else { 510 | root.sha256 = exports.sha256; 511 | root.sha224 = exports.sha224; 512 | if (AMD) { 513 | define(function () { 514 | return exports; 515 | }); 516 | } 517 | } 518 | })(); 519 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const {src, dest} = require('gulp'); 2 | const concat = require('gulp-concat') 3 | 4 | const jsFiles = [ 5 | './genanki/license.js', 6 | './genanki/anki_hash.js', 7 | './genanki/model.js', 8 | './genanki/default.js', 9 | './genanki/deck.js', 10 | './genanki/note.js', 11 | './genanki/package.js', 12 | './genanki/sha256.js', 13 | './genanki/apkg_schema.js', 14 | './genanki/apkg_col.js' 15 | ] 16 | 17 | const bundleJs = () => { 18 | return src(jsFiles) 19 | // bundle all js files in genanki folder in genanki.js file 20 | .pipe(concat('genanki.js')) 21 | // write genanki.js to dist, demo and sample folder 22 | .pipe(dest('./dist')) 23 | .pipe(dest('./docs/demo/js/anki')) 24 | .pipe(dest('./sample/js/anki')) 25 | } 26 | 27 | exports.bundleJs = bundleJs; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "genanki-js", 3 | "version": "1.0.5", 4 | "description": "A JavaScript implementation for generating Anki decks in browser client side", 5 | "main": "genanki.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/krmanik/genanki-js.git" 15 | }, 16 | "keywords": [ 17 | "genanki", 18 | "anki" 19 | ], 20 | "author": "Mani", 21 | "license": "AGPL-3.0-or-later", 22 | "bugs": { 23 | "url": "https://github.com/krmanik/genanki-js/issues" 24 | }, 25 | "homepage": "https://github.com/krmanik/genanki-js#readme" 26 | } 27 | -------------------------------------------------------------------------------- /release.md: -------------------------------------------------------------------------------- 1 | genanki zip file contains two folder 2 | 3 | - `dist` : This folder contains `genanki.js` file. If project already configured with [sql.js](https://github.com/sql-js/sql.js), [FileSaver.js](https://github.com/eligrey/FileSaver.js) and [JSZip](https://github.com/Stuk/jszip) then just add `genanki.js` file to project. 4 | 5 | - `sample` : This folder contains latest [sql.js](https://github.com/sql-js/sql.js), [FileSaver.js](https://github.com/eligrey/FileSaver.js), [JSZip](https://github.com/Stuk/jszip) and [genanki.js](https://github.com/krmanik/genanki-js). It is ready to use folder. 6 | 7 | 8 | View [Documentation](https://krmanik.github.io/genanki-js/) -------------------------------------------------------------------------------- /sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | Add notes and export decks 22 |
23 | 24 |
25 | 26 | 27 |
28 |
Front 29 |
30 | 32 |
33 | 34 |
Back 35 |
36 | 38 |
39 |
40 | 41 |
42 | 43 |
44 | 45 | 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /sample/js/filesaver/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | (function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)}); 2 | 3 | //# sourceMappingURL=FileSaver.min.js.map -------------------------------------------------------------------------------- /sample/js/filesaver/FileSaver.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/FileSaver.js"],"names":[],"mappings":"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","file":"FileSaver.min.js","sourcesContent":["/*\n* FileSaver.js\n* A saveAs() FileSaver implementation.\n*\n* By Eli Grey, http://eligrey.com\n*\n* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)\n* source : http://purl.eligrey.com/github/FileSaver.js\n*/\n\n// The one and only way of getting global scope in all environments\n// https://stackoverflow.com/q/3277182/1008999\nvar _global = typeof window === 'object' && window.window === window\n ? window : typeof self === 'object' && self.self === self\n ? self : typeof global === 'object' && global.global === global\n ? global\n : this\n\nfunction bom (blob, opts) {\n if (typeof opts === 'undefined') opts = { autoBom: false }\n else if (typeof opts !== 'object') {\n console.warn('Deprecated: Expected third argument to be a object')\n opts = { autoBom: !opts }\n }\n\n // prepend BOM for UTF-8 XML and text/* types (including HTML)\n // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF\n if (opts.autoBom && /^\\s*(?:text\\/\\S*|application\\/xml|\\S*\\/\\S*\\+xml)\\s*;.*charset\\s*=\\s*utf-8/i.test(blob.type)) {\n return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })\n }\n return blob\n}\n\nfunction download (url, name, opts) {\n var xhr = new XMLHttpRequest()\n xhr.open('GET', url)\n xhr.responseType = 'blob'\n xhr.onload = function () {\n saveAs(xhr.response, name, opts)\n }\n xhr.onerror = function () {\n console.error('could not download file')\n }\n xhr.send()\n}\n\nfunction corsEnabled (url) {\n var xhr = new XMLHttpRequest()\n // use sync to avoid popup blocker\n xhr.open('HEAD', url, false)\n try {\n xhr.send()\n } catch (e) {}\n return xhr.status >= 200 && xhr.status <= 299\n}\n\n// `a.click()` doesn't work for all browsers (#465)\nfunction click (node) {\n try {\n node.dispatchEvent(new MouseEvent('click'))\n } catch (e) {\n var evt = document.createEvent('MouseEvents')\n evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,\n 20, false, false, false, false, 0, null)\n node.dispatchEvent(evt)\n }\n}\n\n// Detect WebView inside a native macOS app by ruling out all browsers\n// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too\n// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos\nvar isMacOSWebView = /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent)\n\nvar saveAs = _global.saveAs || (\n // probably in some web worker\n (typeof window !== 'object' || window !== _global)\n ? function saveAs () { /* noop */ }\n\n // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView\n : ('download' in HTMLAnchorElement.prototype && !isMacOSWebView)\n ? function saveAs (blob, name, opts) {\n var URL = _global.URL || _global.webkitURL\n var a = document.createElement('a')\n name = name || blob.name || 'download'\n\n a.download = name\n a.rel = 'noopener' // tabnabbing\n\n // TODO: detect chrome extensions & packaged apps\n // a.target = '_blank'\n\n if (typeof blob === 'string') {\n // Support regular links\n a.href = blob\n if (a.origin !== location.origin) {\n corsEnabled(a.href)\n ? download(blob, name, opts)\n : click(a, a.target = '_blank')\n } else {\n click(a)\n }\n } else {\n // Support blobs\n a.href = URL.createObjectURL(blob)\n setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s\n setTimeout(function () { click(a) }, 0)\n }\n }\n\n // Use msSaveOrOpenBlob as a second approach\n : 'msSaveOrOpenBlob' in navigator\n ? function saveAs (blob, name, opts) {\n name = name || blob.name || 'download'\n\n if (typeof blob === 'string') {\n if (corsEnabled(blob)) {\n download(blob, name, opts)\n } else {\n var a = document.createElement('a')\n a.href = blob\n a.target = '_blank'\n setTimeout(function () { click(a) })\n }\n } else {\n navigator.msSaveOrOpenBlob(bom(blob, opts), name)\n }\n }\n\n // Fallback to using FileReader and a popup\n : function saveAs (blob, name, opts, popup) {\n // Open a popup immediately do go around popup blocker\n // Mostly only available on user interaction and the fileReader is async so...\n popup = popup || open('', '_blank')\n if (popup) {\n popup.document.title =\n popup.document.body.innerText = 'downloading...'\n }\n\n if (typeof blob === 'string') return download(blob, name, opts)\n\n var force = blob.type === 'application/octet-stream'\n var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari\n var isChromeIOS = /CriOS\\/[\\d]+/.test(navigator.userAgent)\n\n if ((isChromeIOS || (force && isSafari) || isMacOSWebView) && typeof FileReader !== 'undefined') {\n // Safari doesn't allow downloading of blob URLs\n var reader = new FileReader()\n reader.onloadend = function () {\n var url = reader.result\n url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')\n if (popup) popup.location.href = url\n else location = url\n popup = null // reverse-tabnabbing #460\n }\n reader.readAsDataURL(blob)\n } else {\n var URL = _global.URL || _global.webkitURL\n var url = URL.createObjectURL(blob)\n if (popup) popup.location = url\n else location.href = url\n popup = null // reverse-tabnabbing #460\n setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s\n }\n }\n)\n\n_global.saveAs = saveAs.saveAs = saveAs\n\nif (typeof module !== 'undefined') {\n module.exports = saveAs;\n}\n"]} -------------------------------------------------------------------------------- /sample/js/index.js: -------------------------------------------------------------------------------- 1 | /**************************************************************************************** 2 | * Copyright (c) 2021 Mani * 3 | * * 4 | * * 5 | * This program is free software; you can redistribute it and/or modify it under * 6 | * the terms of the GNU General Public License as published by the Free Software * 7 | * Foundation; either version 3 of the License, or (at your option) any later * 8 | * version. * 9 | * * 10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 12 | * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License along with * 15 | * this program. If not, see . * 16 | * * 17 | * This file incorporates work covered by the following copyright and permission * 18 | * notice: * 19 | * * 20 | * mkanki - generate decks for the Anki spaced-repetition software. * 21 | * Copyright (c) 2018 Jeremy Apthorp * 22 | * * 23 | * This program is free software: you can redistribute it and/or modify * 24 | * it under the terms of the GNU Affero General Public License (version 3) as * 25 | * published by the Free Software Foundation. * 26 | * * 27 | * This program is distributed in the hope that it will be useful, * 28 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 29 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 30 | * GNU Affero General Public License for more details. * 31 | * * 32 | * You should have received a copy of the GNU Affero General Public License * 33 | * along with this program. If not, see . * 34 | ****************************************************************************************/ 35 | 36 | // The `initSqlJs` function is globally provided by all of the main dist files if loaded in the browser. 37 | // We must specify this locateFile function if we are loading a wasm file from anywhere other than the current html page's folder. 38 | 39 | var SQL; 40 | initSqlJs().then(function (sql) { 41 | //Create the database 42 | SQL = sql; 43 | }); 44 | 45 | const m = new Model({ 46 | name: "Basic", 47 | id: "2156341623643", 48 | flds: [ 49 | { name: "Front" }, 50 | { name: "Back" } 51 | ], 52 | req: [ 53 | [0, "all", [0]], 54 | ], 55 | tmpls: [ 56 | { 57 | name: "Card 1", 58 | qfmt: "{{Front}}", 59 | afmt: "{{FrontSide}}\n\n
\n\n{{Back}}", 60 | } 61 | ], 62 | }) 63 | 64 | const d = new Deck(1347617346765, "New deck") 65 | const p = new Package() 66 | 67 | // add note to deck 68 | function addNote() { 69 | var front = document.getElementById("noteFront").value; 70 | var back = document.getElementById("noteBack").value; 71 | 72 | d.addNote(m.note([front, back])) 73 | 74 | document.getElementById("noteBack").value = ""; 75 | document.getElementById("noteFront").value = ""; 76 | } 77 | 78 | // add deck to package and export 79 | function exportDeck() { 80 | p.addDeck(d) 81 | p.writeToFile('deck.apkg') 82 | } --------------------------------------------------------------------------------