mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 18:56:55 -06:00
Merge pull request #4144 from Taha-Mahmoodi/feat/port-double-click-breaks-thread
Type: a double-click on a threaded port breaks the thread there
This commit is contained in:
@@ -50,6 +50,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_thread_selection,
|
||||
remove
|
||||
),
|
||||
cmd!(
|
||||
"text.thread.break",
|
||||
"Break Thread",
|
||||
[],
|
||||
None,
|
||||
"{id, port: \"in\"|\"out\"} break the thread of area type or type on a path `id` before it (its in port) or after it (its out port), as a double-click on that port does: the story flows into the frames before the break, and the frames after it are left empty (still threaded with each other when two or more)",
|
||||
has_doc,
|
||||
break_thread
|
||||
),
|
||||
cmd!(query "text.thread.info", "Threads", [], None, "{} → [[id…]] every thread in order", has_doc, |s, _| {
|
||||
Ok(json!(s.doc()?.doc.text_threads.iter().map(|t| t.iter().map(|i| i.0).collect::<Vec<_>>()).collect::<Vec<_>>()))
|
||||
}),
|
||||
@@ -648,6 +657,56 @@ fn remove(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
ok()
|
||||
}
|
||||
|
||||
/// `text.thread.break`: a double-click on a threaded port. The thread is split there; the whole
|
||||
/// story moves into the frames before the split, and the frames after it are emptied, staying
|
||||
/// threaded with each other when two or more remain.
|
||||
fn break_thread(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "text.thread.break";
|
||||
let id = checked_id(s, p.get("id").unwrap_or(&Value::Null), C)?;
|
||||
let before = match p.get("port").and_then(Value::as_str) {
|
||||
Some("in") => true,
|
||||
Some("out") => false,
|
||||
_ => return Err(bad(C, "`port` must be \"in\" or \"out\"")),
|
||||
};
|
||||
let d = &s.doc()?.doc;
|
||||
let thread = d.text_threads.iter().find(|t| t.contains(&id)).cloned().unwrap_or_default();
|
||||
let at = thread.iter().position(|x| *x == id).map(|i| if before { i } else { i + 1 });
|
||||
let Some(at) = at.filter(|at| *at > 0 && *at < thread.len()) else {
|
||||
return Err(bad(C, if before { "no frame comes before `id` in a thread" } else { "no frame comes after `id` in a thread" }));
|
||||
};
|
||||
let (head, tail) = (thread[..at].to_vec(), thread[at..].to_vec());
|
||||
s.edit("Break Thread", |d, _| {
|
||||
let sources: Vec<TextObject> = thread.iter().filter_map(|id| text_of(d, *id).cloned()).collect();
|
||||
let mut story: Vec<TextRun> = sources.iter().flat_map(|t| t.runs.iter().cloned()).collect();
|
||||
let paras = vectorcraft_text::thread::story_paras(&sources.iter().collect::<Vec<_>>(), &[]);
|
||||
let end_style = story.last().map(|r| r.style.clone()).unwrap_or_default();
|
||||
let last_para = paras.last().cloned().unwrap_or_default();
|
||||
// Every frame but the first holds nothing, for the re-flow to share the story out among
|
||||
// the first part's frames once.
|
||||
for id in head.iter().skip(1).chain(&tail) {
|
||||
if let Some(NodeKind::Text(t)) = d.node_mut(*id).map(|n| &mut n.kind) {
|
||||
t.runs = vec![TextRun { text: String::new(), style: end_style.clone(), inline: None }];
|
||||
t.set_all_paras(last_para.clone());
|
||||
t.first_para_continues = false;
|
||||
refresh_bounds(t);
|
||||
}
|
||||
}
|
||||
if let Some(NodeKind::Text(t)) = head.first().and_then(|id| d.node_mut(*id)).map(|n| &mut n.kind) {
|
||||
vectorcraft_text::edit::normalize(&mut story);
|
||||
t.runs = story;
|
||||
t.set_paragraph_styles(paras);
|
||||
t.first_para_continues = false;
|
||||
refresh_bounds(t);
|
||||
}
|
||||
if let Some(i) = d.text_threads.iter().position(|t| *t == thread) {
|
||||
d.text_threads.splice(i..=i, [head, tail]);
|
||||
}
|
||||
d.text_threads.retain(|t| t.len() >= 2);
|
||||
Ok(())
|
||||
})?;
|
||||
ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1056,6 +1115,68 @@ mod tests {
|
||||
(s, [a, b, c])
|
||||
}
|
||||
|
||||
fn continues(s: &Session, id: u64) -> bool {
|
||||
text_of(&s.doc().unwrap().doc, NodeId(id)).is_some_and(|t| t.first_para_continues)
|
||||
}
|
||||
|
||||
/// A double-click on a threaded port breaks the thread there: the story flows into the frames
|
||||
/// before the break and the frames after it are left empty, still threaded with each other
|
||||
/// when two or more. One undo step restores the thread and its text.
|
||||
#[test]
|
||||
fn break_splits_the_thread_and_the_story_flows_into_the_first_part() {
|
||||
let story = TEXT.repeat(12);
|
||||
let (mut s, [a, b, c]) = three_frames(&story, 40.0);
|
||||
let d = new_id(s.execute("text.create", &json!({"x": 200, "y": 100, "text": "", "area": {"width": 150, "height": 100}})).unwrap());
|
||||
s.execute("text.thread.add", &json!({"from": c, "to": d})).unwrap();
|
||||
let all = [a, b, c, d];
|
||||
let shown: Vec<String> = all.iter().map(|id| text(&s, *id)).collect();
|
||||
assert!(shown.iter().all(|t| !t.is_empty()), "each frame shows part of the story");
|
||||
let joined = |s: &Session, ids: &[u64]| ids.iter().map(|id| text(s, *id)).collect::<String>();
|
||||
// (frame, port, threads after, the frames that hold the story, the frames left empty)
|
||||
let cases = [
|
||||
(a, "out", vec![vec![b, c, d]], vec![a], vec![b, c, d]),
|
||||
(b, "in", vec![vec![b, c, d]], vec![a], vec![b, c, d]),
|
||||
(b, "out", vec![vec![a, b], vec![c, d]], vec![a, b], vec![c, d]),
|
||||
(d, "in", vec![vec![a, b, c]], vec![a, b, c], vec![d]),
|
||||
(c, "out", vec![vec![a, b, c]], vec![a, b, c], vec![d]),
|
||||
];
|
||||
for (id, port, after, full, empty) in cases {
|
||||
let case = format!("{port} port of {id}");
|
||||
s.execute("text.thread.break", &json!({"id": id, "port": port})).unwrap();
|
||||
assert_eq!(threads(&s), after, "{case}");
|
||||
assert_eq!(joined(&s, &full), story, "{case}: the story flows into the first part");
|
||||
assert!(empty.iter().all(|id| text(&s, *id).is_empty()), "{case}: the frames after the break are empty");
|
||||
assert!(!continues(&s, empty[0]), "{case}: the first frame after the break starts a paragraph");
|
||||
assert!(!continues(&s, a), "{case}: the first frame starts the story");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(threads(&s), [all.to_vec()], "{case}: undo");
|
||||
assert_eq!(all.iter().map(|id| text(&s, *id)).collect::<Vec<_>>(), shown, "{case}: undo");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_refuses_a_port_with_no_thread_and_bad_params() {
|
||||
let (mut s, [a, b, c]) = three_frames(&TEXT.repeat(4), 200.0);
|
||||
let lone = new_id(s.execute("text.create", &json!({"x": 400, "y": 10, "text": "Alone.", "area": {"width": 100, "height": 100}})).unwrap());
|
||||
let ids = [a, b, c, lone];
|
||||
let shown: Vec<String> = ids.iter().map(|id| text(&s, *id)).collect();
|
||||
let before = threads(&s);
|
||||
for params in [
|
||||
json!({"id": a, "port": "in"}),
|
||||
json!({"id": c, "port": "out"}),
|
||||
json!({"id": lone, "port": "out"}),
|
||||
json!({"id": lone, "port": "in"}),
|
||||
json!({"id": a, "port": "up"}),
|
||||
json!({"id": a}),
|
||||
json!({"port": "out"}),
|
||||
json!({"id": 9999, "port": "out"}),
|
||||
] {
|
||||
assert!(s.execute("text.thread.break", ¶ms).is_err(), "{params}");
|
||||
}
|
||||
assert_eq!(threads(&s), before);
|
||||
assert_eq!(ids.iter().map(|id| text(&s, *id)).collect::<Vec<_>>(), shown);
|
||||
}
|
||||
|
||||
/// Whether the document holds area type or type on a path `id`.
|
||||
fn is_frame(s: &Session, id: u64) -> bool {
|
||||
text_of(&s.doc().unwrap().doc, NodeId(id)).is_some()
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
//! kind, shape and size at the click on empty canvas, and a drag draws a rectangle for the new
|
||||
//! frame (`text.thread.add`), which comes after the clicked one in its thread. A click on the in
|
||||
//! port does the same with the new frame put before it: the story then starts there and flows on
|
||||
//! into the clicked one. Escape drops the loaded text.
|
||||
//! into the clicked one. Escape drops the loaded text. A double-click on a port where the frame is
|
||||
//! threaded splits the thread at that port (`text.thread.break`), as the archived help section on
|
||||
//! threading describes
|
||||
//! (<https://web.archive.org/web/20220127090642/https://helpx.adobe.com/illustrator/using/creating-text.html>).
|
||||
//! With Smart Guides and Invoke Distance Guides on, Alt/Option held over another object shows how
|
||||
//! far the selection is from it, and over an empty part of an artboard how far it is from the
|
||||
//! artboard's edges ([`crate::guides::distance_guides`]).
|
||||
@@ -498,6 +501,14 @@ impl SelectionTool {
|
||||
fn loaded(&mut self, cx: &ToolContext, ev: &PointerEvent, from: NodeId, before: bool, press: Option<(Point, Point)>) -> Option<Vec<Action>> {
|
||||
let p = ev.pos;
|
||||
match (ev.kind, press) {
|
||||
// The second press of a double-click on the port that loaded the text: the text is
|
||||
// put down, and the double-click breaks the thread there.
|
||||
(PointerKind::Down, _)
|
||||
if crate::text::port_at(cx, p) == Some((from, if before { crate::text::Port::In } else { crate::text::Port::Out })) =>
|
||||
{
|
||||
self.state = State::Idle;
|
||||
return Some(vec![]);
|
||||
}
|
||||
(PointerKind::Down, _) if !cx.doc.node(from).is_some_and(is_threadable) => {
|
||||
self.state = State::Idle;
|
||||
return None;
|
||||
@@ -678,6 +689,16 @@ impl Tool for SelectionTool {
|
||||
match (ev.kind, self.state.clone()) {
|
||||
(PointerKind::DoubleClick, _) => {
|
||||
self.state = State::Idle;
|
||||
// A threaded port: the thread breaks there. Any other port takes the double-click
|
||||
// too, so it neither edits the type nor isolates.
|
||||
if let Some((id, port)) = crate::text::port_at(cx, p) {
|
||||
let (input, output) = crate::text::port_states(cx.doc, id, false);
|
||||
let (state, name) = if port == crate::text::Port::In { (input, "in") } else { (output, "out") };
|
||||
if state == crate::text::PortState::Threaded {
|
||||
return vec![Action::Exec("text.thread.break".into(), json!({ "id": id.0, "port": name }))];
|
||||
}
|
||||
return vec![];
|
||||
}
|
||||
if let Some(a) = corners::double_click(cx, p, false).or_else(|| pie::double_click(cx, p)).or_else(|| typewidget::double_click(cx, p))
|
||||
{
|
||||
return vec![a];
|
||||
@@ -2720,6 +2741,35 @@ mod tests {
|
||||
assert_eq!(t.pointer(&cx, &ev(PointerKind::Down, corner.x, corner.y)), vec![Action::Begin("Resize Type Area".into())]);
|
||||
}
|
||||
|
||||
/// A double-click on a threaded port breaks the thread there, its in port before the frame
|
||||
/// and its out port after it; it doesn't thread the loaded text into a new frame. A
|
||||
/// double-click on a port with no thread does nothing (it doesn't switch to the Type tool).
|
||||
#[test]
|
||||
fn a_double_click_on_a_threaded_port_breaks_the_thread() {
|
||||
let (mut d, id, input, output) = fitting_area_type();
|
||||
let rect = d.children(Some(d.layers[0].id)).unwrap()[0].id;
|
||||
let mut s = Selection::default();
|
||||
s.set([id]);
|
||||
let p = paint();
|
||||
let double_click = |d: &vectorcraft_doc::Document, at: Point| {
|
||||
let cx = cx(d, &s, &p);
|
||||
let mut t = SelectionTool::default();
|
||||
let kinds = [PointerKind::Down, PointerKind::Up, PointerKind::Down, PointerKind::Up, PointerKind::DoubleClick];
|
||||
let out: Vec<Action> = kinds.into_iter().flat_map(|k| t.pointer(&cx, &ev(k, at.x, at.y))).collect();
|
||||
assert_ne!(t.cursor(&cx, Point::new(30.0, 420.0), Mods::default()), Cursor::LoadedText, "no text stays loaded");
|
||||
out
|
||||
};
|
||||
let breaks = |port: &str| vec![Action::Exec("text.thread.break".into(), json!({"id": id.0, "port": port}))];
|
||||
d.text_threads = vec![vec![id, rect]];
|
||||
assert_eq!(double_click(&d, output), breaks("out"));
|
||||
assert_eq!(double_click(&d, input), vec![], "no frame before it");
|
||||
d.text_threads = vec![vec![rect, id]];
|
||||
assert_eq!(double_click(&d, input), breaks("in"));
|
||||
assert_eq!(double_click(&d, output), vec![], "no frame after it");
|
||||
d.text_threads.clear();
|
||||
assert_eq!(double_click(&d, output), vec![]);
|
||||
}
|
||||
|
||||
/// The memo answers for the revision, the selected objects and Use Preview Bounds it measured
|
||||
/// for, and measures again when any of them changes.
|
||||
#[test]
|
||||
|
||||
@@ -10787,6 +10787,36 @@ mod tests {
|
||||
assert!(thread_overlays(&app, zoom).is_empty());
|
||||
}
|
||||
|
||||
/// A double-click with the Selection tool on the out port of a threaded frame breaks the
|
||||
/// thread there: no new frame is threaded on, and the story flows back into the first frame.
|
||||
#[test]
|
||||
fn a_double_click_on_a_threaded_out_port_breaks_the_thread() {
|
||||
use vectorcraft_tools::text::{Port, port_centre};
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 600, "height": 400})).unwrap();
|
||||
let text = "Words that fill more than the frame holds. ".repeat(10);
|
||||
let a =
|
||||
app.run("text.create", json!({"x": 20, "y": 20, "text": text, "area": {"width": 150, "height": 60}})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("text.thread.add", json!({"from": a, "at": [300, 200]})).unwrap();
|
||||
app.run("select.set", json!({"ids": [a]})).unwrap();
|
||||
app.select_tool("selection");
|
||||
let doc = |app: &VectorcraftApp| app.session.active().unwrap().doc.clone();
|
||||
let before = doc(&app);
|
||||
let NodeKind::Text(t) = &before.node(vectorcraft_doc::NodeId(a)).unwrap().kind else { panic!("not text") };
|
||||
let view = app.view_info();
|
||||
let at = port_centre(t, Port::Out, 1.0 / view.zoom).unwrap();
|
||||
let count = doc(&app).layers[0].children().map_or(0, |c| c.len());
|
||||
for kind in [PointerKind::Down, PointerKind::Up, PointerKind::Down, PointerKind::Up, PointerKind::DoubleClick] {
|
||||
crate::canvas::dispatch(&mut app, &PointerEvent::new(kind, at.x, at.y), view);
|
||||
}
|
||||
let d = doc(&app);
|
||||
assert!(d.text_threads.is_empty(), "the thread is broken");
|
||||
assert_eq!(d.layers[0].children().map_or(0, |c| c.len()), count, "no new frame");
|
||||
let NodeKind::Text(t) = &d.node(vectorcraft_doc::NodeId(a)).unwrap().kind else { panic!("not text") };
|
||||
assert_eq!(t.plain_text(), text, "the story flows into the first frame");
|
||||
assert_eq!(app.session.tool_id(), "selection");
|
||||
}
|
||||
|
||||
/// With more than `PORTS_MAX` type objects selected the tools draw no ports, and Show Text
|
||||
/// Threads draws only its lines, no port boxes.
|
||||
#[test]
|
||||
|
||||
@@ -2545,6 +2545,7 @@ font-technology Variable Променливи
|
||||
Remove Plug-in Премахване на добавка
|
||||
Remove Selected Assets Премахване на избраните активи
|
||||
Remove Threading Премахване на свързването
|
||||
Break Thread Прекъсване на свързването
|
||||
Remove “{keyword}” Премахване на „{keyword}“
|
||||
Rename Преименуване
|
||||
Rename Asset Преименуване на актива
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -374,6 +374,7 @@
|
||||
Create Vytvořit
|
||||
Release Selection Uvolnit výběr
|
||||
Remove Threading Odstranit zřetězení
|
||||
Break Thread Přerušit zřetězení
|
||||
Fit Headline Přizpůsobit titulek
|
||||
Find Font… Najít písmo…
|
||||
Change Case Změnit velikost písmen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -336,6 +336,7 @@
|
||||
Create Opret
|
||||
Release Selection Frigør markering
|
||||
Remove Threading Fjern sammenkædning
|
||||
Break Thread Bryd sammenkædning
|
||||
Fit Headline Tilpas overskrift
|
||||
Find Font… Find skrifttype…
|
||||
Change Case Ændr store/små bogstaver
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2566,6 +2566,7 @@ mark-style Japanese Japanisch
|
||||
Remove Plug-in Plug-in entfernen
|
||||
Remove Selected Assets Ausgewählte Assets entfernen
|
||||
Remove Threading Verkettung entfernen
|
||||
Break Thread Verkettung trennen
|
||||
Remove “{keyword}” „{keyword}“ entfernen
|
||||
Rename Umbenennen
|
||||
Rename Asset Asset umbenennen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3144,6 +3144,7 @@ theme Appearance Εμφάνιση
|
||||
Remove Plug-in Αφαίρεση προσθέτου
|
||||
Remove Selected Assets Αφαίρεση επιλεγμένων στοιχείων
|
||||
Remove Threading Αφαίρεση σύνδεσης κειμένου
|
||||
Break Thread Διακοπή σύνδεσης κειμένου
|
||||
Remove “{keyword}” Αφαίρεση «{keyword}»
|
||||
Rename Asset Μετονομασία στοιχείου
|
||||
Rename Character Style Μετονομασία στυλ χαρακτήρων
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2069 and column 19.
|
@@ -2600,6 +2600,7 @@ font-technology Variable Variables
|
||||
Remove Plug-in Quitar plugin
|
||||
Remove Selected Assets Quitar recursos seleccionados
|
||||
Remove Threading Quitar enlace
|
||||
Break Thread Romper enlace
|
||||
Remove “{keyword}” Quitar “{keyword}”
|
||||
Rename Cambiar nombre
|
||||
Rename Asset Cambiar nombre del recurso
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -371,6 +371,7 @@
|
||||
Create Luo
|
||||
Release Selection Vapauta valinta
|
||||
Remove Threading Poista ketjutus
|
||||
Break Thread Katkaise ketjutus
|
||||
Fit Headline Sovita otsikko
|
||||
Find Font… Etsi fontti…
|
||||
Change Case Vaihda kirjainkoko
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2597,6 +2597,7 @@ font-technology Variable Variables
|
||||
Remove Plug-in Supprimer le module externe
|
||||
Remove Selected Assets Supprimer les ressources sélectionnées
|
||||
Remove Threading Supprimer le chaînage
|
||||
Break Thread Rompre le chaînage
|
||||
Remove “{keyword}” Supprimer « {keyword} »
|
||||
Rename Renommer
|
||||
Rename Asset Renommer la ressource
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3126,6 +3126,7 @@ mark-style Japanese Japán
|
||||
Remove Selected Assets Kijelölt elemek eltávolítása
|
||||
Remove the shortcut Billentyűparancs eltávolítása
|
||||
Remove Threading Szövegláncolás megszüntetése
|
||||
Break Thread Szövegláncolás megszakítása
|
||||
Remove “{keyword}” Eltávolítás: „{keyword}”
|
||||
Rename Átnevezés
|
||||
Rename Asset Elem átnevezése
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -336,6 +336,7 @@
|
||||
Create Buat
|
||||
Release Selection Lepaskan seleksi
|
||||
Remove Threading Hapus rangkaian
|
||||
Break Thread Putus rangkaian
|
||||
Fit Headline Sesuaikan judul
|
||||
Find Font… Cari font…
|
||||
Change Case Ubah huruf besar-kecil
|
||||
|
||||
|
@@ -2631,6 +2631,7 @@ mark-style Japanese Giapponese
|
||||
Remove Plug-in Rimuovi plug-in
|
||||
Remove Selected Assets Rimuovi risorse selezionate
|
||||
Remove Threading Rimuovi concatenamento
|
||||
Break Thread Interrompi concatenamento
|
||||
Remove “{keyword}” Rimuovi “{keyword}”
|
||||
Rename Rinomina
|
||||
Rename Asset Rinomina risorsa
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -363,6 +363,7 @@ font-technology Variable バリアブル
|
||||
Create 作成
|
||||
Release Selection 選択部分をリリース
|
||||
Remove Threading スレッドを解除
|
||||
Break Thread スレッドを切断
|
||||
Fit Headline ヘッドラインを合わせる
|
||||
Decrease Font Size フォントサイズを小さく
|
||||
Decrease Font Size by Five Steps フォントサイズを5段階小さく
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -294,6 +294,7 @@
|
||||
Create შექმნა
|
||||
Release Selection მონიშნულის მოხსნა
|
||||
Remove Threading დაკავშირების მოხსნა
|
||||
Break Thread დაკავშირების გაწყვეტა
|
||||
Fit Headline სათაურის მორგება
|
||||
Find Font… შრიფტის ძებნა…
|
||||
Change Case რეგისტრის შეცვლა
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -359,6 +359,7 @@ font-technology Variable 가변
|
||||
Create 만들기
|
||||
Release Selection 선택 해제
|
||||
Remove Threading 연결 제거
|
||||
Break Thread 연결 끊기
|
||||
Fit Headline 제목에 맞추기
|
||||
Decrease Font Size 글꼴 크기 줄이기
|
||||
Decrease Font Size by Five Steps 글꼴 크기 5단계 줄이기
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -350,6 +350,7 @@
|
||||
Create Maken
|
||||
Release Selection Selectie vrijgeven
|
||||
Remove Threading Koppeling opheffen
|
||||
Break Thread Koppeling verbreken
|
||||
Fit Headline Kop passend maken
|
||||
Find Font… Lettertype zoeken…
|
||||
Change Case Hoofdlettergebruik wijzigen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -334,6 +334,7 @@
|
||||
Create Utwórz
|
||||
Release Selection Uwolnij zaznaczenie
|
||||
Remove Threading Usuń wątki
|
||||
Break Thread Przerwij wątek
|
||||
Fit Headline Dopasuj nagłówek
|
||||
Find Font… Znajdź czcionkę…
|
||||
Change Case Zmień wielkość liter
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2149,6 +2149,7 @@
|
||||
Remove Plug-in Remover Plug-in
|
||||
Remove Selected Assets Remover Ativos Selecionados
|
||||
Remove Threading Remover Encadeamento
|
||||
Break Thread Quebrar Encadeamento
|
||||
Remove blank lines between paragraphs Remover linhas em branco entre parágrafos
|
||||
Remove “{keyword}” Remover “{keyword}”
|
||||
Rename Renomear
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2627,6 +2627,7 @@ mark-style Japanese Японский
|
||||
Remove Plug-in Убрать модуль
|
||||
Remove Selected Assets Убрать выбранные ресурсы
|
||||
Remove Threading Убрать связывание
|
||||
Break Thread Разорвать связывание
|
||||
Remove “{keyword}” Убрать «{keyword}»
|
||||
Rename Переименовать
|
||||
Rename Asset Переименовать ресурс
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2545,6 +2545,7 @@ font-technology Variable Variabla
|
||||
Remove Plug-in Ta bort plugin
|
||||
Remove Selected Assets Ta bort markerade resurser
|
||||
Remove Threading Ta bort textlänkning
|
||||
Break Thread Bryt textlänkning
|
||||
Remove “{keyword}” Ta bort ”{keyword}”
|
||||
Rename Byt namn
|
||||
Rename Asset Byt namn på resurs
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -2419,6 +2419,7 @@ font-technology Variable Değişken
|
||||
Remove Plug-in Eklentiyi Kaldır
|
||||
Remove Selected Assets Seçilen Varlıkları Kaldır
|
||||
Remove Threading Diş Açmayı Kaldır
|
||||
Break Thread Metin Bağını Kopar
|
||||
Remove “{keyword}” “{keyword}”yi kaldırın
|
||||
Rename Yeniden adlandır
|
||||
Rename Asset Varlığı Yeniden Adlandır
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -2623,6 +2623,7 @@ mark-style Japanese Японський
|
||||
Remove Plug-in Забрати модуль
|
||||
Remove Selected Assets Забрати вибрані ресурси
|
||||
Remove Threading Прибрати зв’язування
|
||||
Break Thread Розірвати зв’язування
|
||||
Remove “{keyword}” Прибрати "{keyword}"
|
||||
Rename Перейменувати
|
||||
Rename Asset Перейменувати ресурс
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 748 and column 40.
|
@@ -2892,6 +2892,7 @@ mark-style Japanese Kiểu Nhật
|
||||
Remove Plug-in Gỡ plug-in
|
||||
Remove Selected Assets Gỡ các asset đã chọn
|
||||
Remove Threading Gỡ liên kết chuỗi văn bản
|
||||
Break Thread Ngắt chuỗi văn bản
|
||||
Remove “{keyword}” Gỡ “{keyword}”
|
||||
Rename Đổi tên
|
||||
Rename Asset Đổi tên asset
|
||||
|
||||
|
@@ -2500,6 +2500,7 @@ font-script Japanese 汉字
|
||||
Remove Plug-in 移除插件
|
||||
Remove Selected Assets 移除所选资源
|
||||
Remove Threading 移除线程
|
||||
Break Thread 断开串接
|
||||
Remove “{keyword}” 移除“{keyword}”
|
||||
Rename 重命名
|
||||
Rename Asset 重命名资源
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3404,6 +3404,7 @@ font-script Japanese 漢字
|
||||
Remove Plug-in 移除增效模組
|
||||
Remove Selected Assets 移除所選資產
|
||||
Remove Threading 移除串連
|
||||
Break Thread 中斷串連
|
||||
Remove “{keyword}” 移除「{keyword}」
|
||||
Rename 重新命名
|
||||
Rename Asset 重新命名資產
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
+5
-3
File diff suppressed because one or more lines are too long
@@ -4279,6 +4279,13 @@ that doesn't exist, a `from` that is point type, a `to` that is an open path, `f
|
||||
or already in a thread (`from` may be threaded already: the frame goes in beside it), coordinates that aren't finite
|
||||
numbers and a `before` that isn't true or false.
|
||||
|
||||
A double-click on a port where the frame is threaded breaks the thread there (one undo step, `text.thread.break`):
|
||||
|
||||
`text.thread.break {id, port: "in" | "out"}` splits the thread of area type or type on a path `id` before it (`in`) or
|
||||
after it (`out`). The whole story flows into the frames before the break; the frames after it are left empty, still
|
||||
threaded with each other when two or more remain, and the first of them starts a paragraph. It refuses an `id` that
|
||||
doesn't exist, a `port` other than `in` or `out`, and a side where no frame of a thread comes before or after `id`.
|
||||
|
||||
In every thread, a paragraph that continues from one frame into the next has its first line, first-line indent and
|
||||
bullet or number in the frame where it starts. In the next frame its first line starts where its wrapped lines start,
|
||||
and it has no bullet or number. In a frame that is no longer threaded (after `text.thread.releaseSelection` or
|
||||
|
||||
File diff suppressed because one or more lines are too long
+3
-2
File diff suppressed because one or more lines are too long
+3
-2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user