refactor: externalize local upload helpers
Add local-upload-runtime.js as a browser runtime module for upload and attachment helper functions, with sidebar fallback wrappers and a fixed runtime asset route. While validating task479, fix the editor attachment delete path so DOM-selected local attachment links are mapped back to a Tiptap text selection before delete_selection(), preserving undo history and adjacent links.
This commit is contained in:
+17
-9
@@ -360,6 +360,10 @@ function __wbg_get_imports() {
|
||||
const ret = arg0.call(arg1);
|
||||
return ret;
|
||||
}, arguments); },
|
||||
__wbg_call_86e39d65afc3d9db: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
const ret = arg0.call(arg1, arg2, arg3, arg4);
|
||||
return ret;
|
||||
}, arguments); },
|
||||
__wbg_call_a24592a6f349a97e: function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = arg0.call(arg1, arg2);
|
||||
return ret;
|
||||
@@ -846,6 +850,10 @@ function __wbg_get_imports() {
|
||||
state0.a = 0;
|
||||
}
|
||||
},
|
||||
__wbg_new_with_args_57b505df22335acd: function(arg0, arg1, arg2, arg3) {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3));
|
||||
return ret;
|
||||
},
|
||||
__wbg_new_with_byte_offset_and_length_01848e8d6a3d49ad: function(arg0, arg1, arg2) {
|
||||
const ret = new Uint8Array(arg0, arg1 >>> 0, arg2 >>> 0);
|
||||
return ret;
|
||||
@@ -1251,47 +1259,47 @@ function __wbg_get_imports() {
|
||||
}
|
||||
}, arguments); },
|
||||
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1136, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1135, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1188, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1187, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 964, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 963, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__had771ddc65647798);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000004: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1086, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1085, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000005: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1136, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1135, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000006: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 297, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 745, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000007: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1088, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1087, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000008: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1103, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1102, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2b648e7ac8ec1fb5);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000009: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1139, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1138, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0b7fb40a8610550c);
|
||||
return ret;
|
||||
},
|
||||
|
||||
BIN
Binary file not shown.
@@ -1,3 +1,4 @@
|
||||
use leptos_tiptap::TiptapRange;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{window, Element};
|
||||
|
||||
@@ -9,20 +10,17 @@ fn editor_root_element() -> Option<Element> {
|
||||
.and_then(|document| document.query_selector(EDITOR_ROOT_SELECTOR).ok().flatten())
|
||||
}
|
||||
|
||||
/// 当前 DOM 选中文本匹配本地附件链接文本时返回 `true`。
|
||||
pub(crate) fn selected_local_attachment_link_text() -> bool {
|
||||
fn selected_local_attachment_link_dom_info() -> Option<(String, String)> {
|
||||
let selection = match window().and_then(|win| win.get_selection().ok().flatten()) {
|
||||
Some(selection) if !selection.is_collapsed() => selection,
|
||||
_ => return false,
|
||||
_ => return None,
|
||||
};
|
||||
let selected_text = String::from(selection.to_string()).trim().to_string();
|
||||
if selected_text.is_empty() {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(root) = editor_root_element() else {
|
||||
return false;
|
||||
};
|
||||
let root = editor_root_element()?;
|
||||
let links = root.get_elements_by_tag_name("a");
|
||||
for index in 0..links.length() {
|
||||
let Some(link) = links.item(index) else {
|
||||
@@ -33,11 +31,16 @@ pub(crate) fn selected_local_attachment_link_text() -> bool {
|
||||
continue;
|
||||
}
|
||||
if link.text_content().unwrap_or_default().trim() == selected_text {
|
||||
return true;
|
||||
return Some((selected_text, href));
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
None
|
||||
}
|
||||
|
||||
/// 当前 DOM 选中文本匹配本地附件链接文本时返回 `true`。
|
||||
pub(crate) fn selected_local_attachment_link_text() -> bool {
|
||||
selected_local_attachment_link_dom_info().is_some()
|
||||
}
|
||||
|
||||
/// 当前非空选区命中本地附件链接 mark 时返回 `true`。
|
||||
@@ -85,3 +88,54 @@ pub(crate) fn selected_local_attachment_link_in_editor_state() -> bool {
|
||||
pub(crate) fn is_local_attachment_link_selected() -> bool {
|
||||
selected_local_attachment_link_in_editor_state() || selected_local_attachment_link_text()
|
||||
}
|
||||
|
||||
pub(crate) fn selected_local_attachment_link_range() -> Option<TiptapRange> {
|
||||
let (selected_text, selected_href) = selected_local_attachment_link_dom_info()?;
|
||||
let root = editor_root_element()?;
|
||||
let editor_value = js_sys::Reflect::get(root.as_ref(), &JsValue::from_str("editor")).ok()?;
|
||||
let find_range = js_sys::Function::new_with_args(
|
||||
"editor, selectedText, selectedHref",
|
||||
r#"
|
||||
if (!editor || !editor.state || !editor.state.doc) return null;
|
||||
var text = String(selectedText || '').trim();
|
||||
var hrefNeedle = String(selectedHref || '').trim();
|
||||
if (!text) return null;
|
||||
var match = null;
|
||||
editor.state.doc.descendants(function(node, pos) {
|
||||
if (match || !node || !node.isText) return;
|
||||
var nodeText = String(node.text || '');
|
||||
var offset = nodeText.indexOf(text);
|
||||
if (offset < 0) return;
|
||||
var hasAttachmentLink = Array.isArray(node.marks) && node.marks.some(function(mark) {
|
||||
var href = mark && mark.attrs ? String(mark.attrs.href || '') : '';
|
||||
return mark.type && mark.type.name === 'link'
|
||||
&& href.indexOf('/api/local-folder/files/open') >= 0
|
||||
&& (!hrefNeedle || href === hrefNeedle);
|
||||
});
|
||||
if (!hasAttachmentLink) return;
|
||||
match = { from: pos + offset, to: pos + offset + text.length };
|
||||
});
|
||||
return match;
|
||||
"#,
|
||||
);
|
||||
let range_value = find_range
|
||||
.call3(
|
||||
&JsValue::NULL,
|
||||
&editor_value,
|
||||
&JsValue::from_str(&selected_text),
|
||||
&JsValue::from_str(&selected_href),
|
||||
)
|
||||
.ok()?;
|
||||
if range_value.is_null() || range_value.is_undefined() {
|
||||
return None;
|
||||
}
|
||||
let from = js_sys::Reflect::get(&range_value, &JsValue::from_str("from"))
|
||||
.ok()
|
||||
.and_then(|value| value.as_f64())
|
||||
.filter(|value| value.is_finite() && *value >= 0.0)? as u32;
|
||||
let to = js_sys::Reflect::get(&range_value, &JsValue::from_str("to"))
|
||||
.ok()
|
||||
.and_then(|value| value.as_f64())
|
||||
.filter(|value| value.is_finite() && *value >= from as f64)? as u32;
|
||||
Some(TiptapRange { from, to })
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ pub(crate) fn delete_selected_attachment_link_with_history(
|
||||
if !attachment_links::is_local_attachment_link_selected() {
|
||||
return Err("当前选区不是本地附件链接".to_string());
|
||||
}
|
||||
if let Some(range) = attachment_links::selected_local_attachment_link_range() {
|
||||
let _ = editor.set_text_selection(range);
|
||||
}
|
||||
// 继续走 Tiptap 命令路径,保留撤销/重做历史。
|
||||
editor.delete_selection().map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user