mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
cloze refactor wip
This commit is contained in:
parent
20ecd7359d
commit
41fa9a9896
6 changed files with 76 additions and 55 deletions
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from anki.utils import intTime, hexifyID, timestampID
|
from anki.utils import intTime, hexifyID, timestampID
|
||||||
|
from anki.consts import *
|
||||||
|
|
||||||
# Cards
|
# Cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -129,7 +130,11 @@ lapses=?, left=?, odue=?, odid=?, did=? where id = ?""",
|
||||||
return self.col.models.get(self.note().mid)
|
return self.col.models.get(self.note().mid)
|
||||||
|
|
||||||
def template(self):
|
def template(self):
|
||||||
return self.model()['tmpls'][self.ord]
|
m = self.model()
|
||||||
|
if m['type'] == MODEL_STD:
|
||||||
|
return self.model()['tmpls'][self.ord]
|
||||||
|
else:
|
||||||
|
return self.model()['tmpls'][0]
|
||||||
|
|
||||||
def startTimer(self):
|
def startTimer(self):
|
||||||
self.timerStarted = time.time()
|
self.timerStarted = time.time()
|
||||||
|
|
|
@ -266,9 +266,19 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
"Return (active), non-empty templates."
|
"Return (active), non-empty templates."
|
||||||
model = note.model()
|
model = note.model()
|
||||||
avail = self.models.availOrds(model, joinFields(note.fields))
|
avail = self.models.availOrds(model, joinFields(note.fields))
|
||||||
|
return self._tmplsFromOrds(model, avail)
|
||||||
|
|
||||||
|
def _tmplsFromOrds(self, model, avail):
|
||||||
ok = []
|
ok = []
|
||||||
for t in model['tmpls']:
|
if model['type'] == MODEL_STD:
|
||||||
if t['ord'] in avail:
|
for t in model['tmpls']:
|
||||||
|
if t['ord'] in avail:
|
||||||
|
ok.append(t)
|
||||||
|
else:
|
||||||
|
# cloze - generate temporary templates from first
|
||||||
|
for ord in avail:
|
||||||
|
t = copy.copy(model['tmpls'][0])
|
||||||
|
t['ord'] = ord
|
||||||
ok.append(t)
|
ok.append(t)
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
|
@ -304,20 +314,21 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
model = self.models.get(mid)
|
model = self.models.get(mid)
|
||||||
avail = self.models.availOrds(model, flds)
|
avail = self.models.availOrds(model, flds)
|
||||||
did = dids.get(nid) or model['did']
|
did = dids.get(nid) or model['did']
|
||||||
for t in model['tmpls']:
|
# add any missing cards
|
||||||
|
for t in self._tmplsFromOrds(model, avail):
|
||||||
doHave = nid in have and t['ord'] in have[nid]
|
doHave = nid in have and t['ord'] in have[nid]
|
||||||
# if have ord but empty, add cid to remove list
|
if not doHave:
|
||||||
# (may not have nid if generating before any cards added)
|
|
||||||
if doHave and t['ord'] not in avail:
|
|
||||||
rem.append(have[nid][t['ord']])
|
|
||||||
# if missing ord and is available, generate
|
|
||||||
if not doHave and t['ord'] in avail:
|
|
||||||
# we'd like to use the same due# as sibling cards, but we
|
# we'd like to use the same due# as sibling cards, but we
|
||||||
# can't retrieve that quickly, so we give it a new id
|
# can't retrieve that quickly, so we give it a new id
|
||||||
# instead
|
# instead
|
||||||
data.append((ts, nid, t['did'] or did, t['ord'],
|
data.append((ts, nid, t['did'] or did, t['ord'],
|
||||||
now, usn, self.nextID("pos")))
|
now, usn, self.nextID("pos")))
|
||||||
ts += 1
|
ts += 1
|
||||||
|
# note any cards that need removing
|
||||||
|
if nid in have:
|
||||||
|
for ord, id in have[nid].items():
|
||||||
|
if ord not in avail:
|
||||||
|
rem.append(id)
|
||||||
# bulk update
|
# bulk update
|
||||||
self.db.executemany("""
|
self.db.executemany("""
|
||||||
insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
|
insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
|
||||||
|
@ -440,20 +451,28 @@ select id from notes where id in %s and id not in (select nid from cards)""" %
|
||||||
flist = splitFields(data[6])
|
flist = splitFields(data[6])
|
||||||
fields = {}
|
fields = {}
|
||||||
model = self.models.get(data[2])
|
model = self.models.get(data[2])
|
||||||
|
firstName = None
|
||||||
for (name, (idx, conf)) in self.models.fieldMap(model).items():
|
for (name, (idx, conf)) in self.models.fieldMap(model).items():
|
||||||
|
if idx == 0:
|
||||||
|
firstName = name
|
||||||
fields[name] = flist[idx]
|
fields[name] = flist[idx]
|
||||||
fields['Tags'] = data[5]
|
fields['Tags'] = data[5]
|
||||||
fields['Type'] = model['name']
|
fields['Type'] = model['name']
|
||||||
fields['Deck'] = self.decks.name(data[3])
|
fields['Deck'] = self.decks.name(data[3])
|
||||||
template = model['tmpls'][data[4]]
|
if model['type'] == MODEL_STD:
|
||||||
|
template = model['tmpls'][data[4]]
|
||||||
|
else:
|
||||||
|
template = model['tmpls'][0]
|
||||||
fields['Card'] = template['name']
|
fields['Card'] = template['name']
|
||||||
# render q & a
|
# render q & a
|
||||||
d = dict(id=data[0])
|
d = dict(id=data[0])
|
||||||
for (type, format) in (("q", template['qfmt']), ("a", template['afmt'])):
|
for (type, format) in (("q", template['qfmt']), ("a", template['afmt'])):
|
||||||
if type == "q":
|
if type == "q":
|
||||||
format = format.replace("cloze:", "cq:")
|
format = format.replace("{{Cloze}}", "{{cq:%d:%s}}" % (
|
||||||
|
data[4]+1, firstName))
|
||||||
else:
|
else:
|
||||||
format = format.replace("cloze:", "ca:")
|
format = format.replace("{{Cloze}}", "{{ca:%d:%s}}" % (
|
||||||
|
data[4]+1, firstName))
|
||||||
fields = runFilter("mungeFields", fields, model, data, self)
|
fields = runFilter("mungeFields", fields, model, data, self)
|
||||||
html = anki.template.render(format, fields)
|
html = anki.template.render(format, fields)
|
||||||
d[type] = runFilter(
|
d[type] = runFilter(
|
||||||
|
|
|
@ -37,11 +37,15 @@ DYN_FAILED = 5
|
||||||
DYN_ADDED = 6
|
DYN_ADDED = 6
|
||||||
DYN_DUE = 7
|
DYN_DUE = 7
|
||||||
|
|
||||||
|
# model types
|
||||||
|
MODEL_STD = 0
|
||||||
|
MODEL_CLOZE = 1
|
||||||
|
|
||||||
# deck schema & syncing vars
|
# deck schema & syncing vars
|
||||||
SCHEMA_VERSION = 3
|
SCHEMA_VERSION = 4
|
||||||
SYNC_ZIP_SIZE = int(2.5*1024*1024)
|
SYNC_ZIP_SIZE = int(2.5*1024*1024)
|
||||||
SYNC_URL = os.environ.get("SYNC_URL") or "https://beta.ankiweb.net/sync/"
|
SYNC_URL = os.environ.get("SYNC_URL") or "https://beta.ankiweb.net/sync/"
|
||||||
SYNC_VER = 1
|
SYNC_VER = 2
|
||||||
|
|
||||||
# Labels
|
# Labels
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -28,6 +28,7 @@ defaultModel = {
|
||||||
'mod': 0,
|
'mod': 0,
|
||||||
'usn': 0,
|
'usn': 0,
|
||||||
'vers': [],
|
'vers': [],
|
||||||
|
'type': MODEL_STD,
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultField = {
|
defaultField = {
|
||||||
|
@ -425,34 +426,21 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _updateRequired(self, m):
|
def _updateRequired(self, m):
|
||||||
|
if m['type'] == MODEL_CLOZE:
|
||||||
|
# nothing to do
|
||||||
|
return
|
||||||
req = []
|
req = []
|
||||||
flds = [f['name'] for f in m['flds']]
|
flds = [f['name'] for f in m['flds']]
|
||||||
cloze = False
|
|
||||||
for t in m['tmpls']:
|
for t in m['tmpls']:
|
||||||
ret = self._reqForTemplate(m, flds, t)
|
ret = self._reqForTemplate(m, flds, t)
|
||||||
if ret[2]:
|
req.append((t['ord'], ret[0], ret[1]))
|
||||||
cloze = True
|
|
||||||
req.append((t['ord'], ret[0], ret[1], ret[2]))
|
|
||||||
m['req'] = req
|
m['req'] = req
|
||||||
m['cloze'] = cloze
|
|
||||||
|
|
||||||
def _reqForTemplate(self, m, flds, t):
|
def _reqForTemplate(self, m, flds, t):
|
||||||
a = []
|
a = []
|
||||||
b = []
|
b = []
|
||||||
cloze = "cloze" in t['qfmt']
|
|
||||||
reqstrs = []
|
|
||||||
if cloze:
|
|
||||||
# need a cloze-specific filler
|
|
||||||
cloze = ""
|
|
||||||
nums = re.findall("\{\{cloze:(\d+):", t['qfmt'])
|
|
||||||
for n in nums:
|
|
||||||
n = int(n)
|
|
||||||
cloze += "{{c%d::foo}}" % n
|
|
||||||
# record that we require a specific string for generation
|
|
||||||
reqstrs.append("{{c%d::" % n)
|
|
||||||
return 'all', [], reqstrs
|
|
||||||
for f in flds:
|
for f in flds:
|
||||||
a.append(cloze if cloze else "1")
|
a.append("1")
|
||||||
b.append("")
|
b.append("")
|
||||||
data = [1, 1, m['id'], 1, t['ord'], "", joinFields(a)]
|
data = [1, 1, m['id'], 1, t['ord'], "", joinFields(a)]
|
||||||
full = self.col._renderQA(data)['q']
|
full = self.col._renderQA(data)['q']
|
||||||
|
@ -472,7 +460,7 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
if self.col._renderQA(data)['q'] == empty:
|
if self.col._renderQA(data)['q'] == empty:
|
||||||
req.append(i)
|
req.append(i)
|
||||||
if req:
|
if req:
|
||||||
return type, req, reqstrs
|
return type, req
|
||||||
# if there are no required fields, switch to any mode
|
# if there are no required fields, switch to any mode
|
||||||
type = 'any'
|
type = 'any'
|
||||||
req = []
|
req = []
|
||||||
|
@ -483,15 +471,17 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
# if not the same as empty, this field can make the card non-blank
|
# if not the same as empty, this field can make the card non-blank
|
||||||
if self.col._renderQA(data)['q'] != empty:
|
if self.col._renderQA(data)['q'] != empty:
|
||||||
req.append(i)
|
req.append(i)
|
||||||
return type, req, reqstrs
|
return type, req
|
||||||
|
|
||||||
def availOrds(self, m, flds):
|
def availOrds(self, m, flds):
|
||||||
"Given a joined field string, return available template ordinals."
|
"Given a joined field string, return available template ordinals."
|
||||||
|
if m['type'] == MODEL_CLOZE:
|
||||||
|
return self._availClozeOrds(m, flds)
|
||||||
fields = {}
|
fields = {}
|
||||||
for c, f in enumerate(splitFields(flds)):
|
for c, f in enumerate(splitFields(flds)):
|
||||||
fields[c] = f.strip()
|
fields[c] = f.strip()
|
||||||
avail = []
|
avail = []
|
||||||
for ord, type, req, reqstrs in m['req']:
|
for ord, type, req in m['req']:
|
||||||
# unsatisfiable template
|
# unsatisfiable template
|
||||||
if type == "none":
|
if type == "none":
|
||||||
continue
|
continue
|
||||||
|
@ -514,18 +504,14 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
break
|
break
|
||||||
if not ok:
|
if not ok:
|
||||||
continue
|
continue
|
||||||
# extra cloze requirement?
|
|
||||||
ok = True
|
|
||||||
for s in reqstrs:
|
|
||||||
if s not in flds:
|
|
||||||
# required cloze string was missing
|
|
||||||
ok = False
|
|
||||||
break
|
|
||||||
if not ok:
|
|
||||||
continue
|
|
||||||
avail.append(ord)
|
avail.append(ord)
|
||||||
return avail
|
return avail
|
||||||
|
|
||||||
|
def _availClozeOrds(self, m, flds):
|
||||||
|
ret = [int(m)-1 for m in re.findall(
|
||||||
|
"{{c(\d)::[^}]*?}}", splitFields(flds)[0])]
|
||||||
|
return list(set(ret))
|
||||||
|
|
||||||
# Sync handling
|
# Sync handling
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
from anki.consts import MODEL_CLOZE
|
||||||
|
|
||||||
models = []
|
models = []
|
||||||
|
|
||||||
|
@ -31,20 +32,21 @@ models.append((_("Basic"), addBasicModel))
|
||||||
def addClozeModel(col):
|
def addClozeModel(col):
|
||||||
mm = col.models
|
mm = col.models
|
||||||
m = mm.new(_("Cloze"))
|
m = mm.new(_("Cloze"))
|
||||||
fm = mm.newField(("Text"))
|
m['type'] = MODEL_CLOZE
|
||||||
|
fm = mm.newField(_("Text"))
|
||||||
mm.addField(m, fm)
|
mm.addField(m, fm)
|
||||||
for i in range(8):
|
fm = mm.newField(_("Extra"))
|
||||||
n = i+1
|
mm.addField(m, fm)
|
||||||
t = mm.newTemplate(_("Cloze") + " %d" % n)
|
t = mm.newTemplate(_("Cloze"))
|
||||||
fmt = "{{cloze:%d:Text}}" % n
|
fmt = "{{Cloze}}"
|
||||||
t['css'] += """
|
t['css'] += """
|
||||||
.cloze {
|
.cloze {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: blue;
|
color: blue;
|
||||||
}"""
|
}"""
|
||||||
t['qfmt'] = fmt
|
t['qfmt'] = fmt
|
||||||
t['afmt'] = fmt
|
t['afmt'] = fmt + "<br>\n{{%s}}" % _("Extra")
|
||||||
mm.addTemplate(m, t)
|
mm.addTemplate(m, t)
|
||||||
mm.add(m)
|
mm.add(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@ def Collection(path, lock=True, server=False, sync=True):
|
||||||
col.lock()
|
col.lock()
|
||||||
return col
|
return col
|
||||||
|
|
||||||
# no upgrades necessary at the moment
|
|
||||||
def _upgradeSchema(db):
|
def _upgradeSchema(db):
|
||||||
ver = db.scalar("select ver from col")
|
ver = db.scalar("select ver from col")
|
||||||
if ver == SCHEMA_VERSION:
|
if ver == SCHEMA_VERSION:
|
||||||
|
@ -81,6 +80,12 @@ def _upgrade(col, ver):
|
||||||
d['dyn'] = 0
|
d['dyn'] = 0
|
||||||
d['collapsed'] = False
|
d['collapsed'] = False
|
||||||
col.decks.save(d)
|
col.decks.save(d)
|
||||||
|
if ver < 4:
|
||||||
|
for m in col.models.all():
|
||||||
|
if not "{{cloze::" in m['tmpls'][0]['qfmt']:
|
||||||
|
m['type'] = MODEL_STD
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
# Creating a new collection
|
# Creating a new collection
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
Loading…
Reference in a new issue