diff --git a/assets/css/skin/colibris/src/components/popup.css b/assets/css/skin/colibris/src/components/popup.css index 07d85540..d7584cd2 100644 --- a/assets/css/skin/colibris/src/components/popup.css +++ b/assets/css/skin/colibris/src/components/popup.css @@ -49,6 +49,21 @@ .popup .dropdowns-container .nice-select { min-width: 180px; } +.popup .dropdowns-container .dropdown-select-trigger { + min-width: 180px; + padding: 6px 12px; + border: 1px solid var(--middle-color, #d2d2d2); + border-radius: 4px; + background: var(--bg-color, white); + color: var(--text-color, #485365); + font: inherit; + font-size: 14px; + text-align: left; + cursor: pointer; +} +.popup .dropdowns-container .dropdown-select-trigger:hover { + border-color: var(--dark-color, #576273); +} #delete-pad { background-color: #ff7b72; diff --git a/assets/pad/pad.templ b/assets/pad/pad.templ index bb4b9473..76d51bd6 100644 --- a/assets/pad/pad.templ +++ b/assets/pad/pad.templ @@ -96,44 +96,45 @@ templ SettingsPopup(translations map[string]string, availablefonts []string, set

{translations["pad.settings.padSettings"]}

{translations["pad.settings.myView"]}

- - +

- - +

- - +

- - +

- - +

@@ -152,8 +153,7 @@ templ SettingsPopup(translations map[string]string, availablefonts []string, set for _, group := range settingsGroups { for _, item := range group.Items {

- - +

} } @@ -168,8 +168,7 @@ templ EmbedPopup(translations map[string]string) { ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, " Etherpad") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1751,64 +1764,64 @@ func EmbedPopup(translations map[string]string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var124 := templ.GetChildren(ctx) - if templ_7745c5c3_Var124 == nil { - templ_7745c5c3_Var124 = templ.NopComponent + templ_7745c5c3_Var125 := templ.GetChildren(ctx) + if templ_7745c5c3_Var125 == nil { + templ_7745c5c3_Var125 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var125 string - templ_7745c5c3_Var125, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.share"]) + var templ_7745c5c3_Var126 string + templ_7745c5c3_Var126, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.share"]) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 169, Col: 44} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var125)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var126)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "\">

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var127 string - templ_7745c5c3_Var127, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.share.link"]) + var templ_7745c5c3_Var128 string + templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.share.link"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 175, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 174, Col: 53} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var127)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var128)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var128 string - templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.share.emebdcode"]) + var templ_7745c5c3_Var129 string + templ_7745c5c3_Var129, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.share.emebdcode"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 179, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 178, Col: 58} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var128)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var129)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1832,51 +1845,51 @@ func QrPopup(translations map[string]string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var129 := templ.GetChildren(ctx) - if templ_7745c5c3_Var129 == nil { - templ_7745c5c3_Var129 = templ.NopComponent + templ_7745c5c3_Var130 := templ.GetChildren(ctx) + if templ_7745c5c3_Var130 == nil { + templ_7745c5c3_Var130 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var130 string - templ_7745c5c3_Var130, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.share"]) + var templ_7745c5c3_Var131 string + templ_7745c5c3_Var131, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.share"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 189, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 188, Col: 48} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var130)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var131)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "

\"QR
\"QR
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1900,155 +1913,155 @@ func ImportExportPopup(translations map[string]string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var133 := templ.GetChildren(ctx) - if templ_7745c5c3_Var133 == nil { - templ_7745c5c3_Var133 = templ.NopComponent + templ_7745c5c3_Var134 := templ.GetChildren(ctx) + if templ_7745c5c3_Var134 == nil { + templ_7745c5c3_Var134 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var134 string - templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.import_export"]) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 206, Col: 65} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var135 string - templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.import"]) + templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.import_export"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 208, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 204, Col: 65} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var135)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var136 string - templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.abiword.innerHTML"]) + templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.import"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 209, Col: 122} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 206, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var136)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "


") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var137 string - templ_7745c5c3_Var137, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.importSuccessful"]) + templ_7745c5c3_Var137, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.abiword.innerHTML"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 216, Col: 125} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 207, Col: 122} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var137)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "


") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var138 string - templ_7745c5c3_Var138, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.export"]) + templ_7745c5c3_Var138, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.importSuccessful"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 226, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 214, Col: 125} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var138)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var139 string - templ_7745c5c3_Var139, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.exportetherpad"]) + templ_7745c5c3_Var139, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.export"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 228, Col: 151} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 224, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var139)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var140 string - templ_7745c5c3_Var140, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.exporthtml"]) + templ_7745c5c3_Var140, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.importExport.exportetherpad"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 231, Col: 137} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 226, Col: 151} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var140)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, " Markdown
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, " Markdown") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2072,519 +2085,519 @@ func ConnectivityPopup(translations map[string]string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var145 := templ.GetChildren(ctx) - if templ_7745c5c3_Var145 == nil { - templ_7745c5c3_Var145 = templ.NopComponent + templ_7745c5c3_Var146 := templ.GetChildren(ctx) + if templ_7745c5c3_Var146 == nil { + templ_7745c5c3_Var146 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var146 string - templ_7745c5c3_Var146, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.connected"]) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 256, Col: 57} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var146)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var147 string - templ_7745c5c3_Var147, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.reconnecting"]) + templ_7745c5c3_Var147, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.connected"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 259, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 254, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var147)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "


") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var148 string - templ_7745c5c3_Var148, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.userdup"]) + templ_7745c5c3_Var148, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.reconnecting"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 265, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 257, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var148)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "


") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var149 string - templ_7745c5c3_Var149, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.userdup.explanation"]) + templ_7745c5c3_Var149, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.userdup"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 266, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 263, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var149)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var150 string - templ_7745c5c3_Var150, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.userdup.advice"]) + templ_7745c5c3_Var150, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.userdup.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 267, Col: 78} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 264, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var150)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var151 string - templ_7745c5c3_Var151, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.forcereconnect"]) + templ_7745c5c3_Var151, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.userdup.advice"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 268, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 265, Col: 78} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var151)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var153 string - templ_7745c5c3_Var153, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.unauth.explanation"]) + templ_7745c5c3_Var153, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.unauth"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 272, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 269, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var153)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var154 string - templ_7745c5c3_Var154, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.forcereconnect"]) + templ_7745c5c3_Var154, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.unauth.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 273, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 270, Col: 82} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var154)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var156 string - templ_7745c5c3_Var156, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.looping.explanation"]) + templ_7745c5c3_Var156, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 277, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 274, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var156)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var157 string - templ_7745c5c3_Var157, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.looping.cause"]) + templ_7745c5c3_Var157, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.looping.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 278, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 275, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var157)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var158 string - templ_7745c5c3_Var158, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.initsocketfail"]) + templ_7745c5c3_Var158, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.looping.cause"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 281, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 276, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var158)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var159 string - templ_7745c5c3_Var159, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.initsocketfail.explanation"]) + templ_7745c5c3_Var159, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.initsocketfail"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 282, Col: 74} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 279, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var159)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var160 string - templ_7745c5c3_Var160, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.initsocketfail.cause"]) + templ_7745c5c3_Var160, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.initsocketfail.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 283, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 280, Col: 74} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var160)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var161 string - templ_7745c5c3_Var161, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected"]) + templ_7745c5c3_Var161, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.initsocketfail.cause"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 286, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 281, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var161)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var162 string - templ_7745c5c3_Var162, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.slowcommit.explanation"]) + templ_7745c5c3_Var162, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 287, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 284, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var162)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var163 string - templ_7745c5c3_Var163, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.slowcommit.cause"]) + templ_7745c5c3_Var163, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.slowcommit.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 288, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 285, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var163)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var164 string - templ_7745c5c3_Var164, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.forcereconnect"]) + templ_7745c5c3_Var164, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.slowcommit.cause"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 289, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 286, Col: 80} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var164)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var166 string - templ_7745c5c3_Var166, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.badChangeset.explanation"]) + templ_7745c5c3_Var166, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 293, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 290, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var166)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var167 string - templ_7745c5c3_Var167, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.badChangeset.cause"]) + templ_7745c5c3_Var167, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.badChangeset.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 294, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 291, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var167)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var168 string - templ_7745c5c3_Var168, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.forcereconnect"]) + templ_7745c5c3_Var168, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.badChangeset.cause"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 295, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 292, Col: 82} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var168)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var170 string - templ_7745c5c3_Var170, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.corruptPad.explanation"]) + templ_7745c5c3_Var170, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 299, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 296, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var170)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var171 string - templ_7745c5c3_Var171, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.corruptPad.cause"]) + templ_7745c5c3_Var171, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.corruptPad.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 300, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 297, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var171)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var172 string - templ_7745c5c3_Var172, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.deleted"]) + templ_7745c5c3_Var172, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.corruptPad.cause"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 303, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 298, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var172)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var173 string - templ_7745c5c3_Var173, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.deleted.explanation"]) + templ_7745c5c3_Var173, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.deleted"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 304, Col: 66} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 301, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var173)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var174 string - templ_7745c5c3_Var174, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.kicked"]) + templ_7745c5c3_Var174, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.deleted.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 307, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 302, Col: 66} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var174)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var175 string - templ_7745c5c3_Var175, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.kicked.explanation"]) + templ_7745c5c3_Var175, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.kicked"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 308, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 305, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var175)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var176 string - templ_7745c5c3_Var176, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.rateLimited"]) + templ_7745c5c3_Var176, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.kicked.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 311, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 306, Col: 65} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var176)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var177 string - templ_7745c5c3_Var177, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.rateLimited.explanation"]) + templ_7745c5c3_Var177, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.rateLimited"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 312, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 309, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var177)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var178 string - templ_7745c5c3_Var178, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected"]) + templ_7745c5c3_Var178, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.rateLimited.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 315, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 310, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var178)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var179 string - templ_7745c5c3_Var179, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.rejected.explanation"]) + templ_7745c5c3_Var179, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 316, Col: 68} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 313, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var179)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var180 string - templ_7745c5c3_Var180, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.rejected.cause"]) + templ_7745c5c3_Var180, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.rejected.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 317, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 314, Col: 68} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var180)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var181 string - templ_7745c5c3_Var181, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected"]) + templ_7745c5c3_Var181, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.rejected.cause"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 320, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 315, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var181)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var182 string - templ_7745c5c3_Var182, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected.explanation"]) + templ_7745c5c3_Var182, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 321, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 318, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var182)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var183 string - templ_7745c5c3_Var183, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected.cause"]) + templ_7745c5c3_Var183, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected.explanation"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 322, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 319, Col: 72} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var183)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var184 string - templ_7745c5c3_Var184, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.forcereconnect"]) + templ_7745c5c3_Var184, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.modals.disconnected.cause"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 323, Col: 110} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 320, Col: 82} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var184)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2608,51 +2621,51 @@ func UserPopup(translations map[string]string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var185 := templ.GetChildren(ctx) - if templ_7745c5c3_Var185 == nil { - templ_7745c5c3_Var185 = templ.NopComponent + templ_7745c5c3_Var186 := templ.GetChildren(ctx) + if templ_7745c5c3_Var186 == nil { + templ_7745c5c3_Var186 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2676,38 +2689,38 @@ func ChatPopup(translations map[string]string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var189 := templ.GetChildren(ctx) - if templ_7745c5c3_Var189 == nil { - templ_7745c5c3_Var189 = templ.NopComponent + templ_7745c5c3_Var190 := templ.GetChildren(ctx) + if templ_7745c5c3_Var190 == nil { + templ_7745c5c3_Var190 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var191 string - templ_7745c5c3_Var191, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.chat"]) + var templ_7745c5c3_Var192 string + templ_7745c5c3_Var192, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.chat"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 364, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 362, Col: 58} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var191)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var192)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, " 0
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, " 0") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2731,51 +2744,51 @@ func ChatBox(translations map[string]string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var192 := templ.GetChildren(ctx) - if templ_7745c5c3_Var192 == nil { - templ_7745c5c3_Var192 = templ.NopComponent + templ_7745c5c3_Var193 := templ.GetChildren(ctx) + if templ_7745c5c3_Var193 == nil { + templ_7745c5c3_Var193 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var193 string - templ_7745c5c3_Var193, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.chat"]) + var templ_7745c5c3_Var194 string + templ_7745c5c3_Var194, templ_7745c5c3_Err = templ.JoinStringErrs(translations["pad.chat"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 376, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 374, Col: 59} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var193)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var194)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "

█  

█  
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2800,51 +2813,51 @@ func PadIndex(pad padModel.Model, jsScript string, translations map[string]strin }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var196 := templ.GetChildren(ctx) - if templ_7745c5c3_Var196 == nil { - templ_7745c5c3_Var196 = templ.NopComponent + templ_7745c5c3_Var197 := templ.GetChildren(ctx) + if templ_7745c5c3_Var197 == nil { + templ_7745c5c3_Var197 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "<html class=\"pad super-light-toolbar super-light-editor light-background\"><head><title>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var197 string - templ_7745c5c3_Var197, templ_7745c5c3_Err = templ.JoinStringErrs(settings.Title) + var templ_7745c5c3_Var198 string + templ_7745c5c3_Var198, templ_7745c5c3_Err = templ.JoinStringErrs(settings.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 399, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `assets/pad/pad.templ`, Line: 397, Col: 26} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var197)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var198)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "\" rel=\"stylesheet\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2852,7 +2865,7 @@ func PadIndex(pad padModel.Model, jsScript string, translations map[string]strin if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2880,7 +2893,7 @@ func PadIndex(pad padModel.Model, jsScript string, translations map[string]strin if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2896,7 +2909,7 @@ func PadIndex(pad padModel.Model, jsScript string, translations map[string]strin if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/lib/api/static/dev_hmr.go b/lib/api/static/dev_hmr.go index cceb8daf..92a22273 100644 --- a/lib/api/static/dev_hmr.go +++ b/lib/api/static/dev_hmr.go @@ -2,7 +2,7 @@ package static import ( "encoding/json" - "path" + "path/filepath" "strings" "sync" @@ -67,6 +67,23 @@ func (h *esbuildDevHMR) serveBundle(c fiber.Ctx) error { return c.Send(output) } +// buildDevNodePaths exposes pnpm's isolated node_modules folders as fallback +// Node resolution paths. pnpm installs each package's transitive deps inside +// node_modules/.pnpm/@/node_modules/, reachable from the consuming +// package only through a symlink. On Windows the Go resolver in esbuild does +// not always follow those symlinks the same way Node does, so bare imports +// like `lit/decorators.js` coming out of a pnpm-installed library fail to +// resolve. Handing esbuild the isolated folders as NodePaths lets it find +// those deps without relying on symlink resolution. +func buildDevNodePaths(projectRoot string) []string { + pattern := filepath.Join(projectRoot, "node_modules", ".pnpm", "*", "node_modules") + matches, err := filepath.Glob(pattern) + if err != nil { + return nil + } + return matches +} + func buildDevAliases() map[string]string { relativePath := "./src/js" return map[string]string{ @@ -84,6 +101,7 @@ func newDevBundle( name string, entryPoint string, pathToBuild string, + nodePaths []string, notify func(string), ) (*devBundleState, error) { state := &devBundleState{name: name, entryPoint: entryPoint} @@ -118,6 +136,7 @@ func newDevBundle( LogLevel: api.LogLevelInfo, Target: api.ES2020, Alias: buildDevAliases(), + NodePaths: nodePaths, Sourcemap: api.SourceMapInline, Plugins: []api.Plugin{resultPlugin}, }) @@ -151,7 +170,8 @@ func startEsbuildDevHMR(store *lib.InitStore) (*esbuildDevHMR, error) { return nil, nil } - pathToBuild := path.Join(store.RetrievedSettings.Root, "ui") + pathToBuild := filepath.Join(store.RetrievedSettings.Root, "ui") + nodePaths := buildDevNodePaths(store.RetrievedSettings.Root) hmr := &esbuildDevHMR{ logger: store.Logger, bundles: map[string]*devBundleState{}, @@ -173,7 +193,7 @@ func startEsbuildDevHMR(store *lib.InitStore) (*esbuildDevHMR, error) { } for _, spec := range specs { - bundle, err := newDevBundle(store, spec.name, spec.entryPoint, pathToBuild, hmr.notify) + bundle, err := newDevBundle(store, spec.name, spec.entryPoint, pathToBuild, nodePaths, hmr.notify) if err != nil { return nil, err } diff --git a/lib/changeset/TextLinesMutator.go b/lib/changeset/TextLinesMutator.go index 78b85f11..07f0e621 100644 --- a/lib/changeset/TextLinesMutator.go +++ b/lib/changeset/TextLinesMutator.go @@ -4,6 +4,9 @@ import ( "errors" "fmt" "strings" + "unicode/utf8" + + "github.com/ether/etherpad-go/lib/utils" ) // TextLinesMutator is a class to iterate and modify texts which have several lines. @@ -200,10 +203,11 @@ func (m *TextLinesMutator) RemoveLines(L int) string { m.curSplice[1] = deleteCount + L - 1 sline := len(m.curSplice) - 1 slineStr := m.curSplice[sline].(string) - removed = slineStr[m.curCol:] + removed + slineRuneLen := utf8.RuneCountInString(slineStr) + removed = utils.RuneSlice(slineStr, m.curCol, slineRuneLen) + removed startIdx := m.curSplice[0].(int) deleteCount = m.curSplice[1].(int) - m.curSplice[sline] = slineStr[:m.curCol] + m.linesGet(startIdx+deleteCount) + m.curSplice[sline] = utils.RuneSlice(slineStr, 0, m.curCol) + m.linesGet(startIdx+deleteCount) m.curSplice[1] = deleteCount + 1 } } else { @@ -231,14 +235,15 @@ func (m *TextLinesMutator) Remove(N, L int) string { sline := m.putCurLineInSplice() slineStr := m.curSplice[sline].(string) + slineRuneLen := utf8.RuneCountInString(slineStr) endCol := m.curCol + N - if endCol > len(slineStr) { - endCol = len(slineStr) + if endCol > slineRuneLen { + endCol = slineRuneLen } - removed := slineStr[m.curCol:endCol] - m.curSplice[sline] = slineStr[:m.curCol] + slineStr[endCol:] + removed := utils.RuneSlice(slineStr, m.curCol, endCol) + m.curSplice[sline] = utils.RuneSlice(slineStr, 0, m.curCol) + utils.RuneSlice(slineStr, endCol, slineRuneLen) return removed } @@ -260,9 +265,10 @@ func (m *TextLinesMutator) Insert(text string, L int) error { sline := len(m.curSplice) - 1 theLine := m.curSplice[sline].(string) lineCol := m.curCol + theLineRuneLen := utf8.RuneCountInString(theLine) // Insert the chars up to curCol and the first new line - m.curSplice[sline] = theLine[:lineCol] + newLines[0] + m.curSplice[sline] = utils.RuneSlice(theLine, 0, lineCol) + newLines[0] m.curLine++ newLines = newLines[1:] @@ -273,7 +279,7 @@ func (m *TextLinesMutator) Insert(text string, L int) error { m.curLine += len(newLines) // Insert the remaining chars from the "old" line - m.curSplice = append(m.curSplice, theLine[lineCol:]) + m.curSplice = append(m.curSplice, utils.RuneSlice(theLine, lineCol, theLineRuneLen)) m.curCol = 0 } else { for _, line := range newLines { @@ -293,8 +299,9 @@ func (m *TextLinesMutator) Insert(text string, L int) error { } slineStr := m.curSplice[sline].(string) - m.curSplice[sline] = slineStr[:m.curCol] + text + slineStr[m.curCol:] - m.curCol += len(text) + slineRuneLen := utf8.RuneCountInString(slineStr) + m.curSplice[sline] = utils.RuneSlice(slineStr, 0, m.curCol) + text + utils.RuneSlice(slineStr, m.curCol, slineRuneLen) + m.curCol += utf8.RuneCountInString(text) } return nil diff --git a/lib/changeset/inverse_repro_test.go b/lib/changeset/inverse_repro_test.go new file mode 100644 index 00000000..9809f323 --- /dev/null +++ b/lib/changeset/inverse_repro_test.go @@ -0,0 +1,64 @@ +package changeset + +import ( + "testing" + + "github.com/ether/etherpad-go/lib/apool" +) + +// TestInverseRoundTripRev170Repro captures the first failure from the live +// diagnostic at revs 170/171: the inverse of a simple '=' + '+' changeset +// applied to a pad whose text contains 'ü' (multi-byte rune) produces a +// backward changeset that, when applied to the post-forward text, does NOT +// restore the pre-forward text — there is a one-rune transposition near a 'ü'. +// +// Forward : Z:1x>9=1w*0+9$smdüapsmd +// Backward: Z:26<9=1w-9$ +// Pre-forward text (want): asdmasdümasüaüpsdmaüpsüapsdapsdpüasmdüpadüpasmdüpmdüpasdüpaassmmdmdmjg\n +// +// If this test fails, the bug is isolated to the changeset package (either +// Inverse or MutateTextLines handling rune-indexed positions on text that +// contains multi-byte characters). +func TestInverseRoundTripRev170Repro(t *testing.T) { + const forward = "Z:1x>9=1w*0+9$smdüapsmd" + const pre = "asdmasdümasüaüpsdmaüpsüapsdapsdpüasmdüpadüpasmdüpmdüpasdüpaassmmdmdmjg\n" + + preLines := []string{pre} + // alines for a single-attribute-free line of the above length: + // each line's alines entry is a simple "|1+" op describing the line. + // For this repro we use an empty-attribute run that covers the whole line. + pool := apool.NewAPool() + alines := []string{"|1+1x"} // |1 = one newline, +1x = 69 chars (including the \n) + + // 1) Generate the backward changeset from Inverse. + backward, err := Inverse(forward, preLines, alines, &pool) + if err != nil { + t.Fatalf("Inverse failed: %v", err) + } + t.Logf("backward = %s", *backward) + + // 2) Apply forward to a copy of pre and get post-forward. + postLines := append([]string(nil), preLines...) + if err := MutateTextLines(forward, &postLines); err != nil { + t.Fatalf("applying forward failed: %v", err) + } + if len(postLines) == 0 { + t.Fatalf("post-forward lines empty") + } + t.Logf("post-forward = %q", postLines[0]) + + // 3) Apply backward on top of post-forward and compare to pre. + roundTrip := append([]string(nil), postLines...) + if err := MutateTextLines(*backward, &roundTrip); err != nil { + t.Fatalf("applying backward failed: %v", err) + } + + if len(roundTrip) != len(preLines) { + t.Fatalf("round-trip line count: got %d, want %d", len(roundTrip), len(preLines)) + } + for i := range preLines { + if roundTrip[i] != preLines[i] { + t.Errorf("round-trip line %d mismatch:\n got: %q\n want: %q", i, roundTrip[i], preLines[i]) + } + } +} diff --git a/lib/changeset/validate.go b/lib/changeset/validate.go new file mode 100644 index 00000000..f436ef40 --- /dev/null +++ b/lib/changeset/validate.go @@ -0,0 +1,100 @@ +package changeset + +import ( + "fmt" + "unicode/utf8" + + "github.com/ether/etherpad-go/lib/utils" +) + +// ValidateWellFormed walks a changeset and checks per-op invariants that must +// hold for the string to be safely composable with another changeset: +// +// - every op has chars >= lines +// - for '+' ops, the slice of the char bank that the op consumes contains +// exactly op.Lines '\n' characters +// - all char bank characters are consumed by '+' ops (no leftover, no underflow) +// +// It does NOT catch malformed '=' ops whose lines count disagrees with the +// underlying document text — that requires applying the changeset against a +// text buffer. Use ValidateRoundTrip for that class of bug. +// +// A non-nil error describes the first violation found. +func ValidateWellFormed(cs string) error { + unpacked, err := Unpack(cs) + if err != nil { + return fmt.Errorf("unpack: %w", err) + } + ops, err := DeserializeOps(unpacked.Ops) + if err != nil { + return fmt.Errorf("deserialize ops: %w", err) + } + bankRunes := []rune(unpacked.CharBank) + bankLen := len(bankRunes) + bankPos := 0 + for i, op := range *ops { + if op.Chars < op.Lines { + return fmt.Errorf("op %d %q: chars (%d) < lines (%d)", i, op.String(), op.Chars, op.Lines) + } + if op.OpCode == "+" { + if bankPos+op.Chars > bankLen { + return fmt.Errorf("op %d %q: char bank underflow (need %d more, have %d)", i, op.String(), op.Chars, bankLen-bankPos) + } + nls := 0 + for _, r := range bankRunes[bankPos : bankPos+op.Chars] { + if r == '\n' { + nls++ + } + } + if nls != op.Lines { + return fmt.Errorf("op %d %q: char bank slice has %d newline(s), op declares %d", i, op.String(), nls, op.Lines) + } + bankPos += op.Chars + } + } + if bankPos != bankLen { + return fmt.Errorf("char bank length mismatch: consumed %d, bank has %d", bankPos, bankLen) + } + return nil +} + +// ValidateRoundTrip checks that applying `backward` to a copy of +// `postForward` yields `preForward`. This catches `=` ops whose declared +// line count disagrees with the document text — the exact class of bug that +// triggers the client-side "line count mismatch when composing changesets +// A*B" assertion. The caller's slices are not mutated. +func ValidateRoundTrip(backward string, postForward, preForward []string) error { + result := make([]string, len(postForward)) + copy(result, postForward) + if err := safeMutateTextLines(backward, &result); err != nil { + return fmt.Errorf("apply backward: %w", err) + } + if len(result) != len(preForward) { + return fmt.Errorf("round-trip line count mismatch: got %d, want %d", len(result), len(preForward)) + } + for i := range preForward { + if result[i] != preForward[i] { + return fmt.Errorf("round-trip text mismatch at line %d: got %q, want %q", + i, truncate(result[i], 120), truncate(preForward[i], 120)) + } + } + return nil +} + +// safeMutateTextLines wraps MutateTextLines in a recover so that panics from +// malformed changesets surface as errors instead of crashing the caller. +func safeMutateTextLines(cs string, textLines *[]string) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("MutateTextLines panicked: %v", r) + } + }() + return MutateTextLines(cs, textLines) +} + +func truncate(s string, max int) string { + if utf8.RuneCountInString(s) <= max { + return s + } + return utils.RuneSlice(s, 0, max) + "…" +} diff --git a/lib/ws/PadMessageHandler.go b/lib/ws/PadMessageHandler.go index 8817a07b..ee3bfd31 100644 --- a/lib/ws/PadMessageHandler.go +++ b/lib/ws/PadMessageHandler.go @@ -730,6 +730,15 @@ func (p *PadMessageHandler) getChangesetInfo(retrievedPad pad2.Pad, startNum int println("Error getting inverse changeset", err) return nil, err } + + // Snapshot pre-forward text so we can verify that `backwards` truly inverts + // `forwards`. This surfaces the exact revision range responsible for the + // client-side "line count mismatch when composing changesets A*B" + // assertion, which fires when a server-generated '=' op is missing its + // |N line count prefix. + preForwardLines := make([]string, len(lines.TextLines)) + copy(preForwardLines, lines.TextLines) + if err := changeset.MutateAttributionLines(forwards, &lines.Alines, &retrievedPad.Pool); err != nil { println("Error mutating attribution lines", err) return nil, err @@ -742,6 +751,19 @@ func (p *PadMessageHandler) getChangesetInfo(retrievedPad pad2.Pad, startNum int forwards2 := changeset.MoveOpsToNewPool(forwards, &retrievedPad.Pool, &createdApool) backwards2 := changeset.MoveOpsToNewPool(*backwards, &retrievedPad.Pool, &createdApool) + if err := changeset.ValidateWellFormed(forwards2); err != nil { + p.Logger.Errorf("malformed forwards changeset for pad %s revs %d/%d: %v\n forwards: %s", + retrievedPad.Id, compositeStart, compositeEnd, err, forwards2) + } + if err := changeset.ValidateWellFormed(backwards2); err != nil { + p.Logger.Errorf("malformed backwards changeset for pad %s revs %d/%d: %v\n backwards: %s", + retrievedPad.Id, compositeStart, compositeEnd, err, backwards2) + } + if err := changeset.ValidateRoundTrip(*backwards, lines.TextLines, preForwardLines); err != nil { + p.Logger.Errorf("backwards changeset does not invert forwards for pad %s revs %d/%d: %v\n forwards: %s\n backwards: %s", + retrievedPad.Id, compositeStart, compositeEnd, err, forwards, *backwards) + } + var t1 int64 var t2 int64 if compositeStart == 0 { diff --git a/package.json b/package.json index 988ba761..a0f15e3d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,6 @@ "typescript": "^5.6.3" }, "dependencies": { - "etherpad-webcomponents": "^0.0.4" + "etherpad-webcomponents": "^0.0.11" } } diff --git a/playwright/helper/padHelper.ts b/playwright/helper/padHelper.ts index c0bbfb4a..07411655 100644 --- a/playwright/helper/padHelper.ts +++ b/playwright/helper/padHelper.ts @@ -1,16 +1,75 @@ -import {expect, Frame, Locator, Page} from "@playwright/test"; +import {expect, Locator, Page} from "@playwright/test"; import {randomUUID} from "node:crypto"; import os from "node:os"; const isMac = os.platform() === 'darwin'; const modifier = isMac ? 'Meta' : 'Control'; -export const getPadOuter = async (page: Page): Promise => { - return page.frame('ace_outer')!; +// After the WebComponents migration (ui/src/js/ace.ts) the editor no longer +// uses nested iframes — #outerdocbody and #innerdocbody are regular divs in +// the main document. getPadOuter is kept for backwards compatibility with +// specs that only needed a scope; it now returns the page itself as a +// Locator-returning helper. +export const getPadOuter = async (page: Page): Promise => { + return page; } export const getPadBody = async (page: Page): Promise => { - return page.frame('ace_inner')!.locator('#innerdocbody') + return page.locator('#innerdocbody'); +} + +// ep-checkbox is a Lit web component, not a native , +// so Playwright's .check()/.uncheck()/toBeChecked() do not apply. The +// component reflects its state via the `checked` attribute on the host +// element and toggles on click. Use this helper whenever a test needs to +// set or assert the state of an . +export const setEpCheckbox = async (locator: Locator, want: boolean) => { + const isChecked = () => locator.evaluate((el: Element) => el.hasAttribute('checked')); + if ((await isChecked()) !== want) { + await locator.click({force: true}); + } + await expect.poll(isChecked).toBe(want); +} + +export const isEpCheckboxChecked = (locator: Locator): Promise => + locator.evaluate((el: Element) => el.hasAttribute('checked')); + +// is a Lit web component, not a native . Reading .checked + // works on both because the Lit component exposes a reflected `checked` property. + const isReadonly = Boolean((readonlyInput as any)?.checked); + const {link} = this.getShareLinks(isReadonly); qrLinkInput.value = link; - qrImage.src = this.getQrCodeSrc(Boolean(readonlyInput instanceof HTMLInputElement && readonlyInput.checked)); + qrImage.src = this.getQrCodeSrc(isReadonly); } _syncToolbarScrollState() { diff --git a/ui/src/js/pad_editor.ts b/ui/src/js/pad_editor.ts index 603b0373..dfd415da 100644 --- a/ui/src/js/pad_editor.ts +++ b/ui/src/js/pad_editor.ts @@ -44,14 +44,11 @@ export const padeditor = (() => { settings = pad.settings; self.ace = new Ace2Editor(); await self.ace.init('editorcontainer', ''); - // EventBus: emit editor:ace:initialized after the ACE editor is created - editorBus.emit('editor:ace:initialized', {editorInfo: self.ace}); + // editor:ace:initialized is emitted by ace.ts with the shared info object const editorLoading = q('#editorloadingbox'); if (editorLoading) editorLoading.style.display = 'none'; - // Listen for clicks on sidediv items - const outerFrame = q('iframe[name="ace_outer"]'); - const outerDoc = outerFrame?.contentDocument; - const sideDivInner = outerDoc?.querySelector('#sidedivinner'); + // Listen for clicks on sidediv items (now in main document, not iframe) + const sideDivInner = q('#sidedivinner'); sideDivInner?.addEventListener('click', (event) => { const target = event.target; if (!(target instanceof HTMLElement) || target.tagName.toLowerCase() !== 'div') return; @@ -91,10 +88,13 @@ export const padeditor = (() => { // font family change - q('#viewfontmenu')?.addEventListener('change', () => { - const menu = q('#viewfontmenu'); - pad.changeViewOption('padFontFamily', menu?.value); - }); + q('#viewfontmenu')?.addEventListener('ep-dropdown-select', ((e: CustomEvent) => { + const font = e.detail?.value ?? ''; + // Update the trigger button text + const trigger = q('#viewfontmenu [slot="trigger"]'); + if (trigger) trigger.textContent = font || html10n.get('pad.settings.fontType.normal'); + pad.changeViewOption('padFontFamily', font); + }) as EventListener); // delete pad q('#delete-pad')?.addEventListener('click', () => { @@ -118,14 +118,13 @@ export const padeditor = (() => { // Language html10n.bind('localized', () => { - const menu = q('#languagemenu'); - if (menu) menu.value = html10n.getLanguage(); - // translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist + // Update the language trigger button text + const lang = html10n.getLanguage(); + const langItem = q(`#languagemenu ep-dropdown-item[value="${lang}"]`); + const trigger = q('#languagemenu [slot="trigger"]'); + if (trigger && langItem) trigger.textContent = langItem.textContent; - // this does not interfere with html10n's normal value-setting because - // html10n just ingores s - // also, a value which has been set by the user will be not overwritten - // since a user-edited does *not* have the editempty-class + // translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist qa('input[data-l10n-id]').forEach((input) => { if (!(input instanceof HTMLInputElement)) return; if (input.classList.contains('editempty')) { @@ -134,14 +133,18 @@ export const padeditor = (() => { } }); }); - const languageMenu = q('#languagemenu'); - if (languageMenu) languageMenu.value = html10n.getLanguage(); - languageMenu?.addEventListener('change', () => { - const value = languageMenu.value; + // Set initial language trigger text + const langTrigger = q('#languagemenu [slot="trigger"]'); + const currentLang = html10n.getLanguage(); + const currentLangItem = q(`#languagemenu ep-dropdown-item[value="${currentLang}"]`); + if (langTrigger && currentLangItem) langTrigger.textContent = currentLangItem.textContent; + + q('#languagemenu')?.addEventListener('ep-dropdown-select', ((e: CustomEvent) => { + const value = e.detail?.value ?? ''; Cookies.set('language', value, { expires: 36500 }); location.reload(); html10n.localize([value, 'en']); - }); + }) as EventListener); }, setViewOptions: (newOptions) => { const getOption = (key, defaultValue) => { @@ -155,16 +158,27 @@ export const padeditor = (() => { v = getOption('rtlIsTrue', ('rtl' === html10n.getDirection())); self.ace.setProperty('rtlIsTrue', v); + // setProperty on the AceEditor only flips the editor body's direction. + // Etherpad's layout also depends on the dir attribute (the whole + // page flips), so mirror the setting here. The original ace2_inner.ts did + // this inside the editor; in the webcomponent port the editor is scoped + // to its own DOM, so the page-level update lives at the consumer layer. + document.documentElement.dir = v ? 'rtl' : 'ltr'; padutils.setCheckbox('#options-rtlcheck', v); v = getOption('showLineNumbers', true); self.ace.setProperty('showslinenumbers', v); + // #sidediv is outside the editor's scope (it's a sibling in + // #outerdocbody), so the webcomponent AceEditor cannot toggle its + // parent's class the way the original ace2_inner did. + document.getElementById('sidediv') + ?.parentElement?.classList.toggle('line-numbers-hidden', !v); padutils.setCheckbox('#options-linenoscheck', v); v = getOption('showAuthorColors', true); self.ace.setProperty('showsauthorcolors', v); q('#chattext')?.classList.toggle('authorColors', v); - const sideDivInner = q('iframe[name="ace_outer"]')?.contentDocument?.querySelector('#sidedivinner'); + const sideDivInner = q('#sidedivinner'); sideDivInner?.classList.toggle('authorColors', v); padutils.setCheckbox('#options-colorscheck', v); @@ -206,23 +220,16 @@ export const focusOnLine = (ace) => { if (lineNumber[0] === 'L') { const lineNumberInt = parseInt(lineNumber.substr(1)); if (lineNumberInt) { - const outerFrame = q('iframe[name="ace_outer"]'); - const outerDoc = outerFrame?.contentDocument; - const outerDocBody = outerDoc?.querySelector('#outerdocbody'); - const innerFrame = outerDoc?.querySelector('iframe'); - const innerDocBody = innerFrame?.contentDocument?.querySelector('#innerdocbody'); + const innerDocBody = document.getElementById('innerdocbody'); const line = innerDocBody?.querySelector(`div:nth-child(${lineNumberInt})`); - if (line && outerDocBody && innerDocBody) { - let offsetTop = line.getBoundingClientRect().top - innerDocBody.getBoundingClientRect().top; - offsetTop += parseInt(getComputedStyle(outerDocBody).paddingTop.replace('px', '')); - const hasMobileLayout = window.matchMedia('(max-width: 1000px)').matches; - if (!hasMobileLayout) { - offsetTop += parseInt(getComputedStyle(innerDocBody).paddingTop.replace('px', '')); + if (line && innerDocBody) { + const offsetTop = line.getBoundingClientRect().top - innerDocBody.getBoundingClientRect().top; + const editorContainer = document.getElementById('editorcontainer'); + if (editorContainer) { + editorContainer.scrollTop = offsetTop; } - (outerDocBody).style.top = `${offsetTop}px`; // Chrome - outerDoc?.documentElement?.scrollTo({top: offsetTop}); // needed for FF - const node = line; ace.callWithAce((ace) => { + const node = line; const selection = { startPoint: { index: 0, diff --git a/ui/src/js/pad_userlist.ts b/ui/src/js/pad_userlist.ts index 3dcb66cc..411cc368 100644 --- a/ui/src/js/pad_userlist.ts +++ b/ui/src/js/pad_userlist.ts @@ -18,6 +18,7 @@ import padutils from './pad_utils'; import {editorBus} from './core'; import html10n from './i18n'; import {pad} from "./pad.ts"; +import 'etherpad-webcomponents/EpUserBadge.js'; let myUserInfo: Record = {}; @@ -118,7 +119,7 @@ export const paduserlist = (() => { const {scheduleAnimation} = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR); - const NUMCOLS = 4; + const NUMCOLS = 3; const setTdHeight = (tr: HTMLElement, height: number) => { tr.querySelectorAll('td').forEach((td) => { @@ -144,23 +145,21 @@ export const paduserlist = (() => { const replaceUserRowContents = (tr: HTMLElement, height: number, data: any) => { const tds = createUserRowTds(height, data); - if (isNameEditable(data) && tr.querySelector('td.usertdname input:enabled')) { - // preserve input field node - tds.forEach((td, i) => { - const oldTd = tr.querySelectorAll('td')[i]; - if (!oldTd?.classList.contains('usertdname')) oldTd?.replaceWith(td); - }); - } else { - tr.innerHTML = ''; - tr.append(...tds); - } + tr.innerHTML = ''; + tr.append(...tds); return tr; }; const createUserRowTds = (height: number, data: any): HTMLElement[] => { - let name: Node; + const tdBadge = document.createElement('td'); + tdBadge.style.height = `${height}px`; + tdBadge.className = 'usertdswatch'; + tdBadge.colSpan = 2; + const badge = document.createElement('ep-user-badge') as any; + badge.setAttribute('color', padutils.escapeHtml(data.color)); + badge.setAttribute('online', ''); if (data.name) { - name = document.createTextNode(data.name); + badge.setAttribute('name', data.name); } else { const input = document.createElement('input'); input.setAttribute('data-l10n-id', 'pad.userlist.unnamed'); @@ -168,29 +167,18 @@ export const paduserlist = (() => { input.classList.add('editempty', 'newinput'); input.value = html10n.get('pad.userlist.unnamed'); if (isNameEditable(data)) input.disabled = true; - name = input; + badge.setAttribute('name', input.value); + tdBadge.appendChild(input); + input.style.display = 'none'; } - - const tdSwatch = document.createElement('td'); - tdSwatch.style.height = `${height}px`; - tdSwatch.className = 'usertdswatch'; - const swatch = document.createElement('div'); - swatch.className = 'swatch'; - swatch.style.background = padutils.escapeHtml(data.color); - swatch.innerHTML = ' '; - tdSwatch.appendChild(swatch); - - const tdName = document.createElement('td'); - tdName.style.height = `${height}px`; - tdName.className = 'usertdname'; - tdName.append(name); + tdBadge.prepend(badge); const tdActivity = document.createElement('td'); tdActivity.style.height = `${height}px`; tdActivity.className = 'activity'; tdActivity.textContent = data.activity; - return [tdSwatch, tdName, tdActivity]; + return [tdBadge, tdActivity]; }; const createRow = (id: string, contents: HTMLElement[], authorId: string): HTMLElement => { diff --git a/ui/src/js/pad_utils.ts b/ui/src/js/pad_utils.ts index dfd3a1e1..2061e10a 100644 --- a/ui/src/js/pad_utils.ts +++ b/ui/src/js/pad_utils.ts @@ -342,37 +342,25 @@ class PadUtils { return {clear: () => {}}; } getCheckbox = (node: HTMLElement | string) => { - if (typeof node === 'string') { - const el = document.querySelector(node); - return el instanceof HTMLInputElement ? el.checked : false; - } - if (node instanceof HTMLElement) { - return node instanceof HTMLInputElement ? node.checked : false; - } - return false; + const el = typeof node === 'string' ? document.querySelector(node) : node; + if (!el) return false; + if (el.tagName === 'EP-CHECKBOX') return (el as any).checked ?? false; + return el instanceof HTMLInputElement ? el.checked : false; } setCheckbox = (node: HTMLElement | string, value: boolean) => { - if (typeof node === 'string') { - const el = document.querySelector(node); - if (el instanceof HTMLInputElement) el.checked = value; - return; - } - if (node instanceof HTMLElement) { - if (node instanceof HTMLInputElement) node.checked = value; - return; - } + const el = typeof node === 'string' ? document.querySelector(node) : node; + if (!el) return; + if (el.tagName === 'EP-CHECKBOX') { (el as any).checked = value; return; } + if (el instanceof HTMLInputElement) el.checked = value; } bindCheckboxChange = (node: HTMLElement | string, func: Function) => { - if (typeof node === 'string') { - document.querySelector(node)?.addEventListener('change', () => func()); - return; - } - if (node instanceof HTMLElement) { - node.addEventListener('change', () => func()); - return; - } + const el = typeof node === 'string' ? document.querySelector(node) : node; + if (!el) return; + // ep-checkbox fires 'ep-change', native checkbox fires 'change' + const event = el.tagName === 'EP-CHECKBOX' ? 'ep-change' : 'change'; + el.addEventListener(event, () => func()); } encodeUserId = (userId: string) => userId.replace(/[^a-y0-9]/g, (c) => {