fixed #850
This commit is contained in:
parent
647767e436
commit
039a022593
|
@ -93,11 +93,12 @@ public class SpanItem implements Parcelable {
|
|||
SpanItemParcelablePlease.writeToParcel(this, dest, flags);
|
||||
}
|
||||
|
||||
@IntDef({SpanType.HIDE, SpanType.LINK, SpanType.ACCT_MENTION})
|
||||
@IntDef({SpanType.HIDE, SpanType.LINK, SpanType.ACCT_MENTION, SpanType.HASHTAG})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SpanType {
|
||||
int HIDE = -1;
|
||||
int LINK = 0;
|
||||
int ACCT_MENTION = 1;
|
||||
int HASHTAG = 2;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import com.twitter.Regex;
|
|||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.text.AcctMentionSpan;
|
||||
import org.mariotaku.twidere.text.HashtagSpan;
|
||||
import org.mariotaku.twidere.text.TwidereURLSpan;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
|
@ -193,6 +194,8 @@ public final class TwidereLinkify implements Constants {
|
|||
int linkType = type;
|
||||
if (span instanceof AcctMentionSpan) {
|
||||
linkType = LINK_TYPE_USER_ACCT;
|
||||
} else if (span instanceof HashtagSpan) {
|
||||
linkType = LINK_TYPE_HASHTAG;
|
||||
} else if (accountKey != null && USER_TYPE_FANFOU_COM.equals(accountKey.getHost())) {
|
||||
// Fix search path
|
||||
if (url.startsWith("/")) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.text.Spanned
|
|||
import android.text.style.URLSpan
|
||||
import org.mariotaku.twidere.model.SpanItem
|
||||
import org.mariotaku.twidere.text.AcctMentionSpan
|
||||
import org.mariotaku.twidere.text.HashtagSpan
|
||||
import org.mariotaku.twidere.text.ZeroWidthSpan
|
||||
|
||||
val SpanItem.length: Int get() = end - start
|
||||
|
@ -39,6 +40,10 @@ fun Array<SpanItem>.applyTo(spannable: Spannable) {
|
|||
spannable.setSpan(AcctMentionSpan(span.link), span.start, span.end,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
SpanItem.SpanType.HASHTAG -> {
|
||||
spannable.setSpan(HashtagSpan(span.link), span.start, span.end,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
else -> {
|
||||
spannable.setSpan(URLSpan(span.link), span.start, span.end,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.mariotaku.twidere.model.util.ParcelableLocationUtils
|
|||
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
|
||||
import org.mariotaku.twidere.model.util.ParcelableStatusUtils.addFilterFlag
|
||||
import org.mariotaku.twidere.text.AcctMentionSpan
|
||||
import org.mariotaku.twidere.text.HashtagSpan
|
||||
import org.mariotaku.twidere.util.HtmlBuilder
|
||||
import org.mariotaku.twidere.util.HtmlSpanBuilder
|
||||
import org.mariotaku.twidere.util.InternalTwitterContentUtils.getMediaUrl
|
||||
|
@ -278,8 +279,9 @@ internal fun findByOrigRange(spans: Array<SpanItem>, start: Int, end: Int): List
|
|||
internal inline val CharSequence.spanItems get() = (this as? Spanned)?.let { text ->
|
||||
text.getSpans(0, length, URLSpan::class.java).mapToArray {
|
||||
val item = it.toSpanItem(text)
|
||||
if (it is AcctMentionSpan) {
|
||||
item.type = SpanItem.SpanType.ACCT_MENTION
|
||||
when (it) {
|
||||
is AcctMentionSpan -> item.type = SpanItem.SpanType.ACCT_MENTION
|
||||
is HashtagSpan -> item.type = SpanItem.SpanType.HASHTAG
|
||||
}
|
||||
return@mapToArray item
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.mariotaku.twidere.extension.model.api.spanItems
|
|||
import org.mariotaku.twidere.model.*
|
||||
import org.mariotaku.twidere.model.util.ParcelableStatusUtils.addFilterFlag
|
||||
import org.mariotaku.twidere.text.AcctMentionSpan
|
||||
import org.mariotaku.twidere.text.HashtagSpan
|
||||
import org.mariotaku.twidere.util.HtmlEscapeHelper
|
||||
import org.mariotaku.twidere.util.HtmlSpanBuilder
|
||||
import org.mariotaku.twidere.util.emoji.EmojioneTranslator
|
||||
|
@ -132,21 +133,33 @@ private fun calculateDisplayTextRange(text: String, spans: Array<SpanItem>?, med
|
|||
|
||||
object MastodonSpanProcessor : HtmlSpanBuilder.SpanProcessor {
|
||||
|
||||
override fun appendText(text: Editable, buffer: CharArray, start: Int, len: Int): Boolean {
|
||||
override fun appendText(text: Editable, buffer: CharArray, start: Int, len: Int,
|
||||
info: HtmlSpanBuilder.TagInfo?): Boolean {
|
||||
val unescaped = HtmlEscapeHelper.unescape(String(buffer, start, len))
|
||||
text.append(EmojioneTranslator.translate(unescaped))
|
||||
return true
|
||||
}
|
||||
|
||||
override fun applySpan(text: Editable, start: Int, end: Int, info: HtmlSpanBuilder.TagInfo): Boolean {
|
||||
val clsAttr = info.getAttribute("class") ?: return false
|
||||
val hrefAttr = info.getAttribute("href") ?: return false
|
||||
// Is mention or hashtag
|
||||
if ("mention" !in clsAttr.split(" ")) return false
|
||||
if (text[start] != '@') return false
|
||||
text.setSpan(AcctMentionSpan(text.substring(start + 1, end), Uri.parse(hrefAttr).host),
|
||||
start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return true
|
||||
val clsAttr = info.getAttribute("class")?.split(" ") ?: emptyList()
|
||||
when {
|
||||
"tag" in clsAttr || info.isTag(text) -> {
|
||||
text.setSpan(HashtagSpan(text.substring(start, end)), start, end,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return true
|
||||
}
|
||||
"mention" in clsAttr -> {
|
||||
val hrefAttr = info.getAttribute("href") ?: return false
|
||||
if (text[start] != '@') return false
|
||||
text.setSpan(AcctMentionSpan(text.substring(start + 1, end), Uri.parse(hrefAttr).host),
|
||||
start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun HtmlSpanBuilder.TagInfo.isTag(text: CharSequence): Boolean {
|
||||
return start > 0 && text[start - 1] == '#'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -653,7 +653,7 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
|
|||
val status = extras.getParcelable<ParcelableStatus>(EXTRA_STATUS)
|
||||
if (status.account_key.host != accountKey.host) {
|
||||
val composeIntent = Intent(fragment.context, ComposeActivity::class.java)
|
||||
composeIntent.putExtra(Intent.EXTRA_TEXT, " ${LinkCreator.getStatusWebLink(status)}")
|
||||
composeIntent.putExtra(Intent.EXTRA_TEXT, "${status.text_plain } ${LinkCreator.getStatusWebLink(status)}")
|
||||
composeIntent.putExtra(EXTRA_ACCOUNT_KEY, accountKey)
|
||||
composeIntent.putExtra(EXTRA_SELECTION, 0)
|
||||
fragment.startActivity(composeIntent)
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.text
|
||||
|
||||
import android.text.style.URLSpan
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/5/26.
|
||||
*/
|
||||
|
||||
class HashtagSpan(value: String) : URLSpan(value)
|
|
@ -100,7 +100,7 @@ object HtmlSpanBuilder {
|
|||
* @param start Start index of text to append in [buffer]
|
||||
* @param len Length of text to append in [buffer]
|
||||
*/
|
||||
fun appendText(text: Editable, buffer: CharArray, start: Int, len: Int): Boolean = false
|
||||
fun appendText(text: Editable, buffer: CharArray, start: Int, len: Int, info: TagInfo?): Boolean = false
|
||||
|
||||
/**
|
||||
* @param text Text to apply span from [info]
|
||||
|
@ -112,7 +112,8 @@ object HtmlSpanBuilder {
|
|||
|
||||
}
|
||||
|
||||
data class TagInfo(val start: Int, val name: String, val attributes: Map<String, String>?) {
|
||||
data class TagInfo(val start: Int, val name: String, val attributes: Map<String, String>?,
|
||||
val parent: TagInfo? = null) {
|
||||
|
||||
val nameLower = name.toLowerCase(Locale.US)
|
||||
|
||||
|
@ -138,11 +139,12 @@ object HtmlSpanBuilder {
|
|||
) : AbstractSimpleMarkupHandler() {
|
||||
|
||||
private val sb = SpannableStringBuilder()
|
||||
private var tagInfo = ArrayList<TagInfo>()
|
||||
private val tagStack = ArrayList<TagInfo>()
|
||||
private var lastTag: TagInfo? = null
|
||||
|
||||
override fun handleText(buffer: CharArray, offset: Int, len: Int, line: Int, col: Int) {
|
||||
var cur = offset
|
||||
val lastTag = this.lastTag
|
||||
while (cur < offset + len) {
|
||||
// Find first line break
|
||||
var lineBreakIndex = cur
|
||||
|
@ -150,27 +152,27 @@ object HtmlSpanBuilder {
|
|||
if (buffer[lineBreakIndex] == '\n') break
|
||||
lineBreakIndex++
|
||||
}
|
||||
if (!(processor?.appendText(sb, buffer, cur, lineBreakIndex - cur) ?: false)) {
|
||||
if (!(processor?.appendText(sb, buffer, cur, lineBreakIndex - cur, lastTag) ?: false)) {
|
||||
sb.append(HtmlEscapeHelper.unescape(String(buffer, cur, lineBreakIndex - cur)))
|
||||
}
|
||||
cur = lineBreakIndex + 1
|
||||
}
|
||||
lastTag = null
|
||||
this.lastTag = null
|
||||
}
|
||||
|
||||
override fun handleCloseElement(elementName: String, line: Int, col: Int) {
|
||||
val lastIndex = lastIndexOfTag(tagInfo, elementName)
|
||||
val lastIndex = lastIndexOfTag(tagStack, elementName)
|
||||
if (lastIndex == -1) return
|
||||
val info = tagInfo[lastIndex]
|
||||
val info = tagStack[lastIndex]
|
||||
applyTag(sb, info.start, sb.length, info, processor)
|
||||
tagInfo.removeAt(lastIndex)
|
||||
tagStack.removeAt(lastIndex)
|
||||
lastTag = info
|
||||
}
|
||||
|
||||
override fun handleOpenElement(elementName: String, attributes: Map<String, String>?,
|
||||
line: Int, col: Int) {
|
||||
val info = TagInfo(sb.length, elementName, attributes)
|
||||
tagInfo.add(info)
|
||||
val info = TagInfo(sb.length, elementName, attributes, tagStack.lastOrNull())
|
||||
tagStack.add(info)
|
||||
|
||||
// Mastodon case, insert 2 breaks between two <p> tag
|
||||
if ("p" == info.nameLower && "p" == lastTag?.nameLower) {
|
||||
|
@ -181,7 +183,7 @@ object HtmlSpanBuilder {
|
|||
@Throws(ParseException::class)
|
||||
override fun handleStandaloneElement(elementName: String, attributes: Map<String, String>?,
|
||||
minimized: Boolean, line: Int, col: Int) {
|
||||
val info = TagInfo(sb.length, elementName, attributes)
|
||||
val info = TagInfo(sb.length, elementName, attributes, tagStack.lastOrNull())
|
||||
applyTag(sb, info.start, sb.length, info, processor)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue