Skip to content

Commit

Permalink
Dialog mode non-modal, Ctrl-Shift-j join lines, bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
TRIAEIOU committed Oct 16, 2022
1 parent 757beb0 commit 198cfab
Show file tree
Hide file tree
Showing 17 changed files with 175 additions and 214 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,4 @@ It would seem logical to make the Field input editor a Svelte component which im
## Changelog

- 2022-08-27: Add image paste support, prevent focus from being stolen on focus in/out, bug fixes
- 2022-10-16: Make dialog mode non-modal (and allow multiple dialogs open), add `Ctrl-Shift-j` to join lines, bug fixes
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function bundle(input_file, output_file, output_name) {
commonjs(),
nodeResolve({ preferBuiltins: false, browser: true }),
svelte({ include: 'src/**/*.svelte' }),
terser()
//terser()
],
onwarn: (warning, warn) => { // Supress "errounous warnings"
if ((warning.pluginCode === undefined
Expand Down
2 changes: 1 addition & 1 deletion src/py/dialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<bool>true</bool>
</property>
<property name="modal">
<bool>true</bool>
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
Expand Down
155 changes: 72 additions & 83 deletions src/py/dialog_input.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import anki
import aqt, os, json, base64, re
from aqt import mw, gui_hooks
from aqt.utils import *
from aqt.qt import QDialog, QWidget, QObject, QIODevice, QWebEngineScript, QShortcut, QRect, QFile, QUrl
if qtmajor == 6:
Expand All @@ -10,8 +10,8 @@
from .utils import clip_img_to_md

BRIDGECOMMAND_ONACCEPT = "ON_ACCEPT:"

_config = {}
_dlgs = {}

###########################################################################
# Main dialog to edit markdown
Expand All @@ -20,15 +20,18 @@ class IM_dialog(QDialog):
###########################################################################
# Constructor (populates and shows dialog)
###########################################################################
def __init__(self, html: str, parent: QWidget, on_accept: Callable = None, on_reject: Callable = None):
global _config
QDialog.__init__(self, parent)
def __init__(self, parent: aqt.editor.Editor, note: anki.notes.Note, fid: int):
global _config, _dlgs
QDialog.__init__(self)
_dlgs[hex(id(self))] = self
print(">>>" + str(_dlgs))
self.ui = dialog.Ui_dialog()
self.ui.setupUi(self)
self.on_accept = on_accept
self.on_reject = on_reject
self.ui.btns.accepted.connect(self.accept)
self.ui.btns.rejected.connect(self.reject)
self.parent = parent
self.nid = note.id
self.fid = fid

self.setup_bridge(self.bridge)
self.ui.web.setHtml(f'''
Expand All @@ -42,11 +45,20 @@ def __init__(self, html: str, parent: QWidget, on_accept: Callable = None, on_re
<script>
MarkdownInput.converter_init({json.dumps(_config[CONVERTER])});
MarkdownInput.editor_init({json.dumps(_config[EDITOR])});
MarkdownInput.set_html({json.dumps(html)});
MarkdownInput.set_html({json.dumps(note.fields[fid])});
</script>
</body>
</html>
''', QUrl.fromLocalFile(os.path.join(os.path.dirname(__file__), "")))
name = note.items()[0][1] or "[new]"
if len(name) > 15:
name = name[:15] + "..."
self.setWindowTitle(name + ": " + note.items()[fid][0])

def __del__(self):
global _dlgs
_dlgs.pop(hex(id(self)), None)
print("<<<" + str(_dlgs))

###########################################################################
# Setup js → python message bridge
Expand Down Expand Up @@ -100,30 +112,33 @@ def cmd(self, str: str) -> Any:
# Bridge message receiver
###########################################################################
def bridge(self, str = None):
if str.startswith(BRIDGECOMMAND_ONACCEPT):
self.on_accept(str[len(BRIDGECOMMAND_ONACCEPT):])

elif str == "clipboard_image_to_markdown":
if str == "clipboard_image_to_markdown":
img = clip_img_to_md()
return img





###########################################################################
# Main dialog accept
###########################################################################
def accept(self) -> None:
if self.on_accept:
self.ui.web.page().runJavaScript(f'''(function () {{
const html = MarkdownInput.get_html();
pycmd('{BRIDGECOMMAND_ONACCEPT}' + html);
}})();''')

global _config
def save_field(html):
if self.parent.addMode or self.parent.note.id == self.nid:
self.parent.note.fields[self.fid] = html
self.parent.loadNoteKeepingFocus()
else:
note = aqt.mw.col.get_note(self.nid)
note.fields[self.fid] = html
aqt.mw.col.update_note(note)
#note = aqt.mw.col.get_note(self.nid)
#note.fields[self.fid] = html
#aqt.mw.col.update_note(note)

self.ui.web.page().runJavaScript(f'''(function () {{
return MarkdownInput.get_html();
}})();''', save_field)
_config[DIALOG_INPUT][LAST_GEOM] = base64.b64encode(self.saveGeometry()).decode('utf-8')
mw.addonManager.writeConfig(__name__, _config)
aqt.mw.addonManager.writeConfig(__name__, _config)
super().accept()

###########################################################################
Expand All @@ -132,81 +147,55 @@ def accept(self) -> None:
def reject(self):
global _config
_config[DIALOG_INPUT][LAST_GEOM] = base64.b64encode(self.saveGeometry()).decode('utf-8')
mw.addonManager.writeConfig(__name__, _config)
aqt.mw.addonManager.writeConfig(__name__, _config)
super().reject()

###########################################################################
# Open markdown dialog
# Open the Markdown dialog
###########################################################################
def edit_field(editor: aqt.editor.Editor, field: int = None):
def edit_field(editor: aqt.editor.Editor):
global _config

# Run the md editor dialog with callback for accept
def run_dlg(html):
dlg = IM_dialog(html, editor.parentWindow, dlg_result)
if _config[DIALOG_INPUT][SHORTCUT_ACCEPT]:
QShortcut(_config[DIALOG_INPUT][SHORTCUT_ACCEPT], dlg).activated.connect(dlg.accept)
if _config[DIALOG_INPUT][SHORTCUT_REJECT]:
QShortcut(_config[DIALOG_INPUT][SHORTCUT_REJECT], dlg).activated.connect(dlg.reject)

if _config[DIALOG_INPUT][SIZE_MODE].lower() == 'last':
dlg.restoreGeometry(base64.b64decode(_config[DIALOG_INPUT][LAST_GEOM]))
elif match:= re.match(r'^(\d+)x(\d+)', _config[DIALOG_INPUT][SIZE_MODE]):
par_geom = editor.parentWindow.geometry()
geom = QRect(par_geom)
scr_geom = mw.app.primaryScreen().geometry()

geom.setWidth(int(match.group(1)))
geom.setHeight(int(match.group(2)))
if geom.width() > scr_geom.width():
geom.setWidth(scr_geom.width())
if geom.height() > scr_geom.height():
geom.setHeight(scr_geom.height())
geom.moveCenter(par_geom.center())
if geom.x() < 0:
geom.setX(0)
if geom.y() < 0:
geom.setY(0)

dlg.setGeometry(geom)
else:
dlg.setGeometry(editor.parentWindow.geometry())

dlg.show()

# Callback for accepted md dialog
def dlg_result(html):
if _config[DIALOG_INPUT][SELECTION]:
editor.web.eval(f'''(function () {{
MarkdownInput.set_selected_html({field}, {json.dumps(html)});
}})();''')
else:
editor.web.eval(f'''(function () {{
MarkdownInput.set_current_html({field}, {json.dumps(html)});
}})();''')


if field == None:
field = editor.currentField if editor.currentField != None else editor.last_field_index
if field != None: # Get content to edit
if _config[DIALOG_INPUT][SELECTION]: # Extend selection to complete lines and use this
editor.web.evalWithCallback(f'''(function () {{
return MarkdownInput.get_selected_html({field});
}})();
''', run_dlg)
else: # Use entire content
editor.web.evalWithCallback(f'''(function () {{
return MarkdownInput.get_current_html({field});
}})();''', run_dlg)
dlg = IM_dialog(editor, editor.note, editor.currentField)
if _config[DIALOG_INPUT][SHORTCUT_ACCEPT]:
QShortcut(_config[DIALOG_INPUT][SHORTCUT_ACCEPT], dlg).activated.connect(dlg.accept)
if _config[DIALOG_INPUT][SHORTCUT_REJECT]:
QShortcut(_config[DIALOG_INPUT][SHORTCUT_REJECT], dlg).activated.connect(dlg.reject)

if _config[DIALOG_INPUT][SIZE_MODE].lower() == 'last':
dlg.restoreGeometry(base64.b64decode(_config[DIALOG_INPUT][LAST_GEOM]))
elif match:= re.match(r'^(\d+)x(\d+)', _config[DIALOG_INPUT][SIZE_MODE]):
par_geom = editor.parentWindow.geometry()
geom = QRect(par_geom)
scr_geom = aqt.mw.app.primaryScreen().geometry()

geom.setWidth(int(match.group(1)))
geom.setHeight(int(match.group(2)))
if geom.width() > scr_geom.width():
geom.setWidth(scr_geom.width())
if geom.height() > scr_geom.height():
geom.setHeight(scr_geom.height())
geom.moveCenter(par_geom.center())
if geom.x() < 0:
geom.setX(0)
if geom.y() < 0:
geom.setY(0)

dlg.setGeometry(geom)
else:
dlg.setGeometry(editor.parentWindow.geometry())

dlg.show()



###########################################################################
# Configure and activate dialog Markdown input
def init(cfg: object):
global _config
_config = cfg

gui_hooks.editor_did_init_shortcuts.append(lambda shortcuts, editor:
aqt.gui_hooks.editor_did_init_shortcuts.append(lambda shortcuts, editor:
shortcuts.append([
QKeySequence(_config[DIALOG_INPUT][SHORTCUT]),
lambda ed=editor: edit_field(ed)
Expand Down
2 changes: 1 addition & 1 deletion src/py/dialog_qt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def setupUi(self, dialog):
dialog.setLayoutDirection(QtCore.Qt.LeftToRight)
dialog.setAutoFillBackground(False)
dialog.setSizeGripEnabled(True)
dialog.setModal(True)
dialog.setModal(False)
self.verticalLayout_2 = QtWidgets.QVBoxLayout(dialog)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.web = QtWebEngineWidgets.QWebEngineView(dialog)
Expand Down
2 changes: 1 addition & 1 deletion src/py/dialog_qt6.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def setupUi(self, dialog):
dialog.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
dialog.setAutoFillBackground(False)
dialog.setSizeGripEnabled(True)
dialog.setModal(True)
dialog.setModal(False)
self.verticalLayout_2 = QtWidgets.QVBoxLayout(dialog)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.web = QtWebEngineWidgets.QWebEngineView(dialog)
Expand Down
2 changes: 1 addition & 1 deletion src/py/field_input.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json, tempfile
from aqt import mw, gui_hooks, QKeySequence, operations
from aqt import mw, gui_hooks, QKeySequence
from aqt.utils import *
from .constants import *
from .utils import clip_img_to_md
Expand Down
38 changes: 38 additions & 0 deletions src/ts/CodeMirror.extensions/joinLines.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {EditorView} from "@codemirror/view"
const doJoinLines = (view: EditorView) => {
const selection = view.state.selection
const trs = []
const text = view.state.doc.toString()
selection.ranges.forEach((rng, n) => {
const to = rng.empty ? text.length : rng.to
const tin = text.substring(rng.from, to)
const tout = rng.empty
? tin.replace(/\s*\n[\n\s]*/, ' ')
: tin.replace(/\s*\n[\n\s]*/g, ' ')
if (tout !== tin)
trs.push({
changes: {
from: rng.from, to: to,
insert: tout
}
})
})

if (!trs.length) return false
view.dispatch(...trs)
return true

}

// Create extension with current ordinal
function joinLines(options: {} = {}): [] {
return []
}

// Keyboard shortcuts
const joinLinesKeymap = [
{ key: 'Ctrl-Shift-j', run: doJoinLines }
]


export {joinLines, joinLinesKeymap}
2 changes: 1 addition & 1 deletion src/ts/Unified/correct-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function correctMdastList(h: HastH, nd: Element, pt: HastParent) {
const cds = []
nd.children.forEach((cd: HastElement, i) => {
if (cd.tagName === 'ul' || cd.tagName === 'ol') {
if (i) cds[i - 1].children.push(cd)
if (i) cds[cds.length - 1].children?.push(cd)
else cds.push({type: 'element', tagName: 'li', children: [cd]})
} else cds.push(cd)
})
Expand Down
2 changes: 1 addition & 1 deletion src/ts/Unified/table-newline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function hastCellTableNewline(cell: any, sym: string) {
if (i !== tlen - 1) cds.push(br)
})
} else {
if (_nd.children.length) replace(_nd)
if (_nd.children?.length) replace(_nd)
cds.push(_nd)
}
})
Expand Down
20 changes: 20 additions & 0 deletions src/ts/Unified/tableBlockInlineHtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {toHtml} from 'hast-util-to-html'
import {list as _list} from 'hast-util-to-mdast/lib/handlers/list'
import type {Content as MdastContent} from 'mdast'
import type {H as HastH} from 'hast-util-to-mdast'

/**
* Convert inline media (audio/video) hast node to mdast
*/
function listHastToMdast(h: HastH, node: any): void | MdastContent | MdastContent[] {
return h.inTable
? h(node, 'html', toHtml(node, {allowDangerousHtml: true}))
: _list(h, node)
}

const tableBlockInlineHtmlHastHandler = {
ul: listHastToMdast,
ol: listHastToMdast
}

export {tableBlockInlineHtmlHastHandler}
Loading

0 comments on commit 198cfab

Please sign in to comment.