Skip to content

Commit 2eb3147

Browse files
authored
Merge pull request #742 from wordpress-mobile/feature/Add-creating-links-open-new-window
Add support for creating links that open in a new window
2 parents c2b3b25 + afc67c9 commit 2eb3147

File tree

6 files changed

+100
-13
lines changed

6 files changed

+100
-13
lines changed

app/src/androidTest/kotlin/org/wordpress/aztec/demo/pages/EditLinkPage.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ class EditLinkPage : BasePage() {
1818
private var okButton: ViewInteraction
1919
private var cancelButton: ViewInteraction
2020
private var removeButton: ViewInteraction
21+
private var openInNewWindowCheckbox: ViewInteraction
2122

2223
override val trait: ViewInteraction
2324
get() = onView(withText("Insert link"))
2425

2526
init {
2627
urlField = onView(withId(R.id.linkURL))
2728
nameField = onView(withId(R.id.linkText))
29+
openInNewWindowCheckbox = onView(withId(R.id.openInNewWindow))
2830
okButton = onView(withId(android.R.id.button1))
2931
cancelButton = onView(withId(android.R.id.button2))
3032
removeButton = onView(withId(android.R.id.button3))
@@ -58,6 +60,12 @@ class EditLinkPage : BasePage() {
5860
return this
5961
}
6062

63+
fun toggleOpenInNewWindow(): EditLinkPage {
64+
openInNewWindowCheckbox.perform(click())
65+
label("Toggled open in a new window checkbox")
66+
return this
67+
}
68+
6169
fun ok() {
6270
okButton.perform(click())
6371
label("Inserted link")

app/src/androidTest/kotlin/org/wordpress/aztec/demo/tests/LinkTests.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ class LinkTests : BaseTest() {
3333
.verifyHTML(html)
3434
}
3535

36+
@Test
37+
fun testAddLinkWithOpenExternal() {
38+
val text = "sample link"
39+
val url = "https://github.com/wordpress-mobile/AztecEditor-Android"
40+
val html = "<a target=\"_blank\" rel=\"noopener\" href=\"$url\">$text</a>"
41+
42+
EditorPage()
43+
.makeLink()
44+
45+
EditLinkPage()
46+
.updateURL(url)
47+
.updateName(text)
48+
.toggleOpenInNewWindow()
49+
.ok()
50+
51+
EditorPage()
52+
.toggleHtml()
53+
.verifyHTML(html)
54+
}
55+
3656
@Test
3757
fun testMixedLinkFormatting() {
3858
val text1 = "sample "
@@ -100,6 +120,28 @@ class LinkTests : BaseTest() {
100120
.verifyHTML(html)
101121
}
102122

123+
@Test
124+
fun testToggleOpenInNewWindowLink() {
125+
val text = "sample link"
126+
val url1 = "https://github.com/wordpress-mobile/AztecEditor-Android"
127+
val link = "<a href=\"$url1\" rel=\"noopener\" target=\"_blank\">$text</a>"
128+
val html = "<a href=\"$url1\">$text</a>"
129+
130+
EditorPage()
131+
.toggleHtml()
132+
.insertHTML(link)
133+
.toggleHtml()
134+
.makeLink()
135+
136+
EditLinkPage()
137+
.toggleOpenInNewWindow()
138+
.ok()
139+
140+
EditorPage()
141+
.toggleHtml()
142+
.verifyHTML(html)
143+
}
144+
103145
@Test
104146
fun testRemoveLink() {
105147
val text = "sample link"

aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import android.view.MotionEvent
5252
import android.view.View
5353
import android.view.WindowManager
5454
import android.view.inputmethod.BaseInputConnection
55+
import android.widget.CheckBox
5556
import android.widget.EditText
5657
import org.wordpress.android.util.AppLog
5758
import org.wordpress.android.util.ImageUtils
@@ -128,6 +129,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
128129
val LINK_DIALOG_VISIBLE_KEY = "LINK_DIALOG_VISIBLE_KEY"
129130
val LINK_DIALOG_URL_KEY = "LINK_DIALOG_URL_KEY"
130131
val LINK_DIALOG_ANCHOR_KEY = "LINK_DIALOG_ANCHOR_KEY"
132+
val LINK_DIALOG_OPEN_NEW_WINDOW_KEY = "LINK_DIALOG_OPEN_NEW_WINDOW_KEY"
131133

132134
val HISTORY_LIST_KEY = "HISTORY_LIST_KEY"
133135
val HISTORY_CURSOR_KEY = "HISTORY_CURSOR_KEY"
@@ -595,8 +597,8 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
595597
if (isLinkDialogVisible) {
596598
val retainedUrl = customState.getString(LINK_DIALOG_URL_KEY, "")
597599
val retainedAnchor = customState.getString(LINK_DIALOG_ANCHOR_KEY, "")
598-
599-
showLinkDialog(retainedUrl, retainedAnchor)
600+
val retainedOpenInNewWindow = customState.getString(LINK_DIALOG_OPEN_NEW_WINDOW_KEY, "")
601+
showLinkDialog(retainedUrl, retainedAnchor, retainedOpenInNewWindow)
600602
}
601603

602604
val isBlockEditorDialogVisible = customState.getBoolean(BLOCK_DIALOG_VISIBLE_KEY, false)
@@ -643,9 +645,11 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
643645

644646
val urlInput = addLinkDialog!!.findViewById<EditText>(R.id.linkURL)
645647
val anchorInput = addLinkDialog!!.findViewById<EditText>(R.id.linkText)
648+
val openInNewWindowCheckbox = addLinkDialog!!.findViewById<CheckBox>(R.id.openInNewWindow)
646649

647650
bundle.putString(LINK_DIALOG_URL_KEY, urlInput?.text?.toString())
648651
bundle.putString(LINK_DIALOG_ANCHOR_KEY, anchorInput?.text?.toString())
652+
bundle.putString(LINK_DIALOG_OPEN_NEW_WINDOW_KEY, if (openInNewWindowCheckbox != null && openInNewWindowCheckbox.isChecked) "checked=true" else "checked=false")
649653
}
650654

651655
if (blockEditorDialog != null && blockEditorDialog!!.isShowing) {
@@ -1421,14 +1425,14 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
14211425
}
14221426
}
14231427

1424-
fun link(url: String, anchor: String) {
1428+
fun link(url: String, anchor: String, openInNewWindow: Boolean = false) {
14251429
history.beforeTextChanged(this@AztecText)
14261430
if (TextUtils.isEmpty(url) && linkFormatter.isUrlSelected()) {
14271431
removeLink()
14281432
} else if (linkFormatter.isUrlSelected()) {
1429-
linkFormatter.editLink(url, anchor, linkFormatter.getUrlSpanBounds().first, linkFormatter.getUrlSpanBounds().second)
1433+
linkFormatter.editLink(url, anchor, openInNewWindow, linkFormatter.getUrlSpanBounds().first, linkFormatter.getUrlSpanBounds().second)
14301434
} else {
1431-
linkFormatter.addLink(url, anchor, selectionStart, selectionEnd)
1435+
linkFormatter.addLink(url, anchor, openInNewWindow, selectionStart, selectionEnd)
14321436
}
14331437
}
14341438

@@ -1451,21 +1455,24 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
14511455
}
14521456

14531457
@SuppressLint("InflateParams")
1454-
fun showLinkDialog(presetUrl: String = "", presetAnchor: String = "") {
1458+
fun showLinkDialog(presetUrl: String = "", presetAnchor: String = "", presetOpenInNewWindow: String = "" ) {
14551459
val urlAndAnchor = linkFormatter.getSelectedUrlWithAnchor()
14561460

14571461
val url = if (TextUtils.isEmpty(presetUrl)) urlAndAnchor.first else presetUrl
14581462
val anchor = if (TextUtils.isEmpty(presetAnchor)) urlAndAnchor.second else presetAnchor
1463+
val openInNewWindow = if (TextUtils.isEmpty(presetOpenInNewWindow)) urlAndAnchor.third else presetOpenInNewWindow == "checked=true"
14591464

14601465
val builder = AlertDialog.Builder(context)
14611466

14621467
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_link, null)
14631468

14641469
val urlInput = dialogView.findViewById<EditText>(R.id.linkURL)
14651470
val anchorInput = dialogView.findViewById<EditText>(R.id.linkText)
1471+
val openInNewWindowCheckbox = dialogView.findViewById<CheckBox>(R.id.openInNewWindow)
14661472

14671473
urlInput.setText(url)
14681474
anchorInput.setText(anchor)
1475+
openInNewWindowCheckbox.isChecked = openInNewWindow
14691476

14701477
builder.setView(dialogView)
14711478
builder.setTitle(R.string.link_dialog_title)
@@ -1474,7 +1481,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
14741481
val linkText = TextUtils.htmlEncode(correctUrl(urlInput.text.toString().trim { it <= ' ' }))
14751482
val anchorText = anchorInput.text.toString().trim { it <= ' ' }
14761483

1477-
link(linkText, anchorText)
1484+
link(linkText, anchorText, openInNewWindowCheckbox.isChecked)
14781485
})
14791486

14801487
if (linkFormatter.isUrlSelected()) {

aztec/src/main/kotlin/org/wordpress/aztec/formatting/LinkFormatter.kt

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ class LinkFormatter(editor: AztecText, val linkStyle: LinkStyle) : AztecFormatte
1919
return !urlSpans.isEmpty()
2020
}
2121

22-
fun getSelectedUrlWithAnchor(): Pair<String, String> {
22+
fun getSelectedUrlWithAnchor(): Triple<String, String, Boolean> {
2323
val url: String
2424
var anchor: String
25+
var openInNewWindow = false
2526

2627
if (!isUrlSelected()) {
2728
val clipboardUrl = getUrlFromClipboard(editor.context)
@@ -46,9 +47,11 @@ class LinkFormatter(editor: AztecText, val linkStyle: LinkStyle) : AztecFormatte
4647
if (anchor == url) {
4748
anchor = ""
4849
}
50+
51+
openInNewWindow = if (urlSpan.attributes.hasAttribute("target")) urlSpan.attributes.getValue("target") == ("_blank") else false
4952
}
5053

51-
return Pair(url, anchor)
54+
return Triple(url, anchor, openInNewWindow)
5255
}
5356

5457
/**
@@ -78,13 +81,16 @@ class LinkFormatter(editor: AztecText, val linkStyle: LinkStyle) : AztecFormatte
7881
return Pair(spanStart, spanEnd)
7982
}
8083

81-
fun addLink(link: String, anchor: String, start: Int, end: Int) {
84+
fun addLink(link: String, anchor: String, openInNewWindow: Boolean = false, start: Int, end: Int) {
8285
val cleanLink = link.trim()
8386

8487
val actualAnchor = if (TextUtils.isEmpty(anchor)) cleanLink else anchor
8588

8689
val ssb = SpannableStringBuilder(actualAnchor)
87-
setLinkSpan(ssb, cleanLink, 0, actualAnchor.length)
90+
val attributes = getAttributes(end, start)
91+
toggleOpenInNewWindowAttributes(openInNewWindow, attributes)
92+
93+
setLinkSpan(ssb, cleanLink, 0, actualAnchor.length, attributes)
8894

8995
if (start == end) {
9096
// insert anchor
@@ -94,12 +100,25 @@ class LinkFormatter(editor: AztecText, val linkStyle: LinkStyle) : AztecFormatte
94100
if (editor.getSelectedText() != anchor) {
95101
editableText.replace(start, end, ssb)
96102
} else {
97-
setLinkSpan(editableText, cleanLink, start, end)
103+
setLinkSpan(editableText, cleanLink, start, end, attributes)
98104
}
99105
}
100106
}
101107

102-
fun editLink(link: String, anchor: String?, start: Int = selectionStart, end: Int = selectionEnd) {
108+
private fun toggleOpenInNewWindowAttributes(openInNewWindow: Boolean = false, attributes: AztecAttributes = AztecAttributes()): AztecAttributes {
109+
if (openInNewWindow) {
110+
attributes.setValue("target", "_blank")
111+
attributes.setValue("rel", "noopener")
112+
} else {
113+
attributes.removeAttribute("target")
114+
if (attributes.hasAttribute("rel") && attributes.getValue("rel") == "noopener") {
115+
attributes.removeAttribute("rel")
116+
}
117+
}
118+
return attributes
119+
}
120+
121+
fun editLink(link: String, anchor: String?, openInNewWindow: Boolean = false, start: Int = selectionStart, end: Int = selectionEnd) {
103122
val cleanLink = link.trim()
104123
val newEnd: Int
105124

@@ -116,6 +135,7 @@ class LinkFormatter(editor: AztecText, val linkStyle: LinkStyle) : AztecFormatte
116135

117136
val attributes = getAttributes(end, start)
118137
attributes.setValue("href", cleanLink)
138+
toggleOpenInNewWindowAttributes(openInNewWindow, attributes)
119139

120140
linkValid(cleanLink, start, newEnd, attributes)
121141
}

aztec/src/main/res/layout/dialog_link.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,13 @@
3838
android:hint="@string/link_enter_url_text"
3939
android:inputType="text" />
4040
</android.support.design.widget.TextInputLayout>
41+
<CheckBox
42+
android:id="@+id/openInNewWindow"
43+
android:text="@string/link_open_new_window"
44+
android:layout_width="match_parent"
45+
android:layout_height="wrap_content"
46+
android:layout_marginBottom="@dimen/link_dialog_margin_outer"
47+
android:layout_marginLeft="@dimen/link_dialog_margin_outer"
48+
android:layout_marginRight="@dimen/link_dialog_margin_outer"
49+
android:layout_marginTop="@dimen/link_dialog_margin_inner" />
4150
</LinearLayout>

aztec/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<string name="link_enter_url">URL</string>
5050
<string name="link_enter_url_text">Link text (optional)</string>
5151
<string name="create_a_link">Create a link</string>
52+
<string name="link_open_new_window">Open link in a new window/tab</string>
5253

5354
<!-- MEDIA -->
5455
<string name="error_chooser_photo">No app was found to choose a photo</string>

0 commit comments

Comments
 (0)