├── .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 += `
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 |
45 | genanki-js
46 |
48 |
49 |
50 |
51 |
67 |
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 | Run
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
Generate decks from csv and tsv files
149 |
150 |
151 | 1. Import csv or tsv file
152 |
153 |
154 | 2. Enter fields name, and select fields to be added to front back or both
155 |
156 |
157 | 3. Export deck
158 |
159 |
160 |
Note: If downloaded file name ends with
.zip then remove it and make extension of file
161 |
.apkg
162 |
163 | For e.g. Export-Deck.apkg.zip ---> Export-Deck.apkg
164 |
165 |
166 |
167 | 4. Dev tools can be used to check if there any error in exporting decks.
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
Select a CSV/TSV file
176 |
177 |
178 |
179 |
180 |
181 |
Fields Separated By
182 |
183 | TAB
184 | COMMA
185 |
186 |
187 |
188 |
189 |
Import
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | Enter Deck Name
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
Export
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
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 | Add
45 | Export
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 | }
--------------------------------------------------------------------------------