From 6ae0ef4df9b7241774f7242440f59c2ddd24664b Mon Sep 17 00:00:00 2001 From: tateisu Date: Tue, 4 Feb 2020 06:14:40 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=81=AE=E5=85=A5=E5=8A=9B=E8=A3=9C=E5=AE=8C=E3=81=A7IDN?= =?UTF-8?q?=E3=83=89=E3=83=A1=E3=82=A4=E3=83=B3=E3=82=92=E9=81=B8=E6=8A=9E?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=81=A8punycode=E3=82=92=E5=85=A5=E5=8A=9B?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=80=82MMF=E3=83=91=E3=83=BC=E3=82=B5?= =?UTF-8?q?=E3=81=AF=E3=83=87=E3=82=B3=E3=83=BC=E3=83=89=E7=B5=90=E6=9E=9C?= =?UTF-8?q?=E3=81=ABIDN=E3=83=89=E3=83=A1=E3=82=A4=E3=83=B3=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/jp/juggler/subwaytooter/ActPost.kt | 43 ++++++------- .../subwaytooter/action/Action_User.kt | 8 ++- .../juggler/subwaytooter/api/entity/Acct.kt | 50 ++++++++-------- .../util/MisskeyMarkdownDecoder.kt | 60 +++++++++---------- .../util/PopupAutoCompleteAcct.kt | 15 +++-- 5 files changed, 87 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index 689c2bfd..ff78d247 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -744,7 +744,8 @@ class ActPost : AppCompatActivity(), sv = intent.getStringExtra(KEY_REPLY_STATUS) if(sv != null && account != null) { try { - val reply_status = TootParser(this@ActPost, account).status(sv.decodeJsonObject()) + val reply_status = + TootParser(this@ActPost, account).status(sv.decodeJsonObject()) val isQuoterRenote = intent.getBooleanExtra(KEY_QUOTED_RENOTE, false) @@ -764,35 +765,28 @@ class ActPost : AppCompatActivity(), } // 新しいメンションリスト - val mention_list = ArrayList() + val mention_list = ArrayList() // 自己レス以外なら元レスへのメンションを追加 // 最初に追加する https://github.com/tateisu/SubwayTooter/issues/94 if(! account.isMe(reply_status.account)) { - mention_list.add("@${account.getFullAcct(reply_status.account).pretty}") + mention_list.add(account.getFullAcct(reply_status.account)) } // 元レスに含まれていたメンションを複製 reply_status.mentions?.forEach { mention -> - + val who_acct = mention.acct - + // 空データなら追加しない - if( ! who_acct.isValid ) return@forEach - + if(! who_acct.isValid) return@forEach + // 自分なら追加しない if(account.isMe(who_acct)) return@forEach - - // 既出なら追加しない - val strMention = "@${account.getFullAcct(who_acct).pretty}" - if(mention_list.contains(strMention)) return@forEach - mention_list.add(strMention) - - /* - FIXME インスタンスのバージョンが3.1.0 以降ならメンションのホスト部分にIDNドメインを使いたいが、 - 投稿画面でのアカウント切り替え時にタンスのバージョンが異なると破綻する可能性が高い。 - */ + // 既出でないなら追加する + val acct = account.getFullAcct(who_acct) + if(! mention_list.contains(acct)) mention_list.add(acct) } if(mention_list.isNotEmpty()) { @@ -800,7 +794,7 @@ class ActPost : AppCompatActivity(), StringBuilder().apply { for(acct in mention_list) { if(isNotEmpty()) append(' ') - append(acct) + append("@${acct.ascii}") } append(' ') }.toString() @@ -852,7 +846,8 @@ class ActPost : AppCompatActivity(), sv = intent.getStringExtra(KEY_REDRAFT_STATUS) if(sv != null && account != null) { try { - val base_status = TootParser(this@ActPost, account).status(sv.decodeJsonObject()) + val base_status = + TootParser(this@ActPost, account).status(sv.decodeJsonObject()) if(base_status != null) { redraft_status_id = base_status.id @@ -1060,12 +1055,12 @@ class ActPost : AppCompatActivity(), val array = attachment_list // アップロード完了したものだけ保持する - .filter{it.status == PostAttachment.STATUS_UPLOADED } - .mapNotNull { it.attachment?.encodeJson() } + .filter { it.status == PostAttachment.STATUS_UPLOADED } + .mapNotNull { it.attachment?.encodeJson() } .toJsonArray() .notEmpty() - if(array != null ) outState.putString(KEY_ATTACHMENT_LIST, array.toString()) + if(array != null) outState.putString(KEY_ATTACHMENT_LIST, array.toString()) in_reply_to_id?.putTo(outState, KEY_IN_REPLY_TO_ID) outState.putString(KEY_IN_REPLY_TO_TEXT, in_reply_to_text) @@ -1511,7 +1506,7 @@ class ActPost : AppCompatActivity(), ) { ai -> // 別タンスのアカウントに変更したならならin_reply_toの変換が必要 - if(in_reply_to_id != null && ! ai.matchHost(account?.host) ) { + if(in_reply_to_id != null && ! ai.matchHost(account?.host)) { startReplyConversion(ai) } else { setAccountWithVisibilityConversion(ai) @@ -2672,7 +2667,7 @@ class ActPost : AppCompatActivity(), try { val tmp_attachment_list = attachment_list - .mapNotNull{it.attachment?.encodeJson()} + .mapNotNull { it.attachment?.encodeJson() } .toJsonArray() val json = JsonObject() diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_User.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_User.kt index 5b9a8299..dcbdbf2e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_User.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_User.kt @@ -632,7 +632,9 @@ object Action_User { // メンションを含むトゥートを作る private fun mention( - activity : ActMain, account : SavedAccount, initial_text : String + activity : ActMain, + account : SavedAccount, + initial_text : String ) { ActPost.open( activity, @@ -646,7 +648,7 @@ object Action_User { fun mention( activity : ActMain, account : SavedAccount, who : TootAccount ) { - mention(activity, account, "@${account.getFullAcct(who).pretty} ") + mention(activity, account, "@${account.getFullAcct(who).ascii} ") } // メンションを含むトゥートを作る @@ -656,7 +658,7 @@ object Action_User { if(who == null) return val who_host = who.host - val initial_text = "@${access_info.getFullAcct(who).pretty} " + val initial_text = "@${access_info.getFullAcct(who).ascii} " AccountPicker.pick( activity, bAllowPseudo = false, diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/Acct.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/Acct.kt index fe6d86f5..353d1e98 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/Acct.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/Acct.kt @@ -8,7 +8,7 @@ import java.util.concurrent.ConcurrentHashMap class Host private constructor( val ascii : String, val pretty : String = ascii -) :Comparable{ +) : Comparable { override fun toString() : String = ascii @@ -16,28 +16,28 @@ class Host private constructor( ascii.hashCode() override fun equals(other : Any?) : Boolean = - if( other is Host) ascii==other.ascii else false - + if(other is Host) ascii == other.ascii else false + // ソートはprettyのコード順 override fun compareTo(other : Host) : Int = pretty.compareTo(other.pretty).notZero() ?: ascii.compareTo(other.ascii) fun match(src : String?) : Boolean = - ascii==src || pretty ==src + ascii == src || pretty == src - val isValid :Boolean + val isValid : Boolean get() = ascii.isNotEmpty() && ascii != "?" - fun valid() : Host? = if(isValid ) this else null + fun valid() : Host? = if(isValid) this else null companion object { // declare this first! private val hostSet = ConcurrentHashMap() - + val EMPTY = Host("") val UNKNOWN = Host("?") val FRIENDS_NICO = Host("friends.nico") - + fun parse(src : String) : Host { val cached = hostSet[src] if(cached != null) return cached @@ -53,17 +53,17 @@ class Host private constructor( class Acct private constructor( val username : String, val host : Host? -) :Comparable{ +) : Comparable { - val ascii:String = if(host==null) username else "$username@${host.ascii}" - val pretty:String = if(host==null) username else "$username@${host.pretty}" + val ascii : String = if(host == null) username else "$username@${host.ascii}" + val pretty : String = if(host == null) username else "$username@${host.pretty}" override fun toString() : String = ascii - + override fun hashCode() : Int = ascii.hashCode() override fun equals(other : Any?) : Boolean = - if( other is Acct) ascii == other.ascii else false + if(other is Acct) ascii == other.ascii else false // ソートはprettyのコード順 override fun compareTo(other : Acct) : Int { @@ -71,45 +71,45 @@ class Acct private constructor( } fun followHost(accessHost : Host?) : Acct { - return if( this.host != null) this else Acct(username,accessHost) + return if(this.host != null) this else Acct(username, accessHost) } val isValid : Boolean get() = username.isNotEmpty() && username != "?" && (host?.isValid != false) - + @Suppress("unused") - private fun valid():Acct? = if(isValid) this else null - + private fun valid() : Acct? = if(isValid) this else null + private val isValidFull : Boolean get() = username.isNotEmpty() && username != "?" && (host?.isValid == true) - fun validFull():Acct ? = if(isValidFull) this else null + fun validFull() : Acct? = if(isValidFull) this else null companion object { // declare this first! private val acctSet = ConcurrentHashMap() - - val UNKNOWN = Acct("?",Host.UNKNOWN) + + val UNKNOWN = Acct("?", Host.UNKNOWN) fun parse(src : String) : Acct { val cached = acctSet[src] if(cached != null) return cached - if( src.isEmpty()) return UNKNOWN + if(src.isEmpty()) return UNKNOWN val pos = src.indexOf('@') val acct = if(pos != - 1) Acct(src.substring(0, pos), Host.parse(src.substring(pos + 1))) else Acct(src, null) - + acctSet[src] = acct return acct } - - fun parse(user : String,host:String?) = + + fun parse(user : String, host : String?) = if(host != null) parse("$user@$host") else parse(user) - fun parse(user : String,host:Host?) =Acct(user,host) + fun parse(user : String, host : Host?) = Acct(user, host) } } \ No newline at end of file diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt index 161eecae..91f66599 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt @@ -12,9 +12,12 @@ import android.text.style.StrikethroughSpan import android.util.SparseArray import android.util.SparseBooleanArray import jp.juggler.subwaytooter.ActMain +import jp.juggler.subwaytooter.Pref import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.api.entity.TootAccount import jp.juggler.subwaytooter.api.entity.TootMention +import jp.juggler.subwaytooter.api.entity.Acct +import jp.juggler.subwaytooter.api.entity.Host import jp.juggler.subwaytooter.span.* import jp.juggler.subwaytooter.table.AcctColor import jp.juggler.subwaytooter.table.HighlightWord @@ -844,38 +847,25 @@ object MisskeyMarkdownDecoder { MENTION({ val username = it.args[0] - val host = it.args[1] + val strHost = it.args[1].notEmpty() + val rawAcct = Acct.parse(username, strHost?.let{ Host.parse(it)}) val linkHelper = linkHelper if(linkHelper == null) { - appendText( - when { - host.isEmpty() -> "@$username" - else -> "@$username@$host" - } - - ) + appendText("@${rawAcct.pretty}") } else { - - when( - val userHost = (host.notEmpty() - ?: linkHelper.host?.ascii - ?: "?" - ).toLowerCase(Locale.JAPAN) - ) { - + when( strHost ){ // https://github.com/syuilo/misskey/pull/3603 - "github.com", "twitter.com" -> { appendLink( - "@$username@$userHost", - "https://$userHost/$username" // no @ + "@${rawAcct.pretty}", + "https://$strHost/$username" // no @ ) } "gmail.com" -> { appendLink( - "@$username@$userHost", - "mailto:$username@$userHost" + "@${rawAcct.pretty}", + "mailto:$username@$strHost" ) } @@ -883,23 +873,25 @@ object MisskeyMarkdownDecoder { // MFMはメンションからユーザのURIを調べる正式な方法がない // たとえば @group_dev_jp@gup.pe の正式なURLは https://gup.pe/u/group_dev_jp // だが、 misskey.io ではメンションのリンク先は https://misskey.io/@group_dev_jp@gup.pe になる - val userUrl = "https://$userHost/@$username" - - val shortAcct = when { - - host.isEmpty() || linkHelper.host?.match(host) == true -> username - - else -> "$username@$host" + + val fullAcct = rawAcct.followHost(linkHelper.host) + + val shortAcct = if(fullAcct.host != linkHelper.host) { + fullAcct + } else { + Acct.parse(username) } + + val userUrl = "https://${fullAcct.ascii}/@${username.encodePercent()}" val mentions = prepareMentions() - if(null == mentions.find { m -> m.acct.ascii == shortAcct || m.acct.pretty == shortAcct }) { + if(null == mentions.find { m -> m.acct == shortAcct }) { mentions.add( TootMention( jp.juggler.subwaytooter.api.entity.EntityId.DEFAULT , userUrl - , shortAcct + , shortAcct.ascii , username ) ) @@ -907,9 +899,11 @@ object MisskeyMarkdownDecoder { appendLink( when { - jp.juggler.subwaytooter.Pref.bpMentionFullAcct(jp.juggler.subwaytooter.App1.pref) -> - "@$username@$userHost" - else -> "@$shortAcct" + Pref.bpMentionFullAcct(jp.juggler.subwaytooter.App1.pref) -> + "@${fullAcct.pretty}" + else -> { + "@${rawAcct.pretty}" + } } , userUrl ) diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt index ebe208c4..60b80913 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt @@ -15,12 +15,14 @@ import android.widget.LinearLayout import android.widget.PopupWindow import jp.juggler.subwaytooter.Pref import jp.juggler.subwaytooter.R +import jp.juggler.subwaytooter.api.entity.Acct import jp.juggler.subwaytooter.view.MyEditText import jp.juggler.util.LogCategory import jp.juggler.util.getAttributeColor import jp.juggler.util.groupEx import java.util.* import java.util.regex.Pattern +import kotlin.math.min @SuppressLint("InflateParams") internal class PopupAutoCompleteAcct( @@ -135,8 +137,8 @@ internal class PopupAutoCompleteAcct( val sb = SpannableStringBuilder() val src_length = editable.length - start = Math.min(src_length, sel_start) - val end = Math.min(src_length, sel_end) + start = min(src_length, sel_start) + val end = min(src_length, sel_end) sb.append(editable.subSequence(0, start)) val remain = editable.subSequence(end, src_length) @@ -147,8 +149,13 @@ internal class PopupAutoCompleteAcct( sb.append(findShortCode(acct.toString())) // セパレータにZWSPを使う設定なら、補完した次の位置にもZWSPを追加する。連続して入力補完できるようになる。 if( separator != ' ') sb.append(separator) - } else { - // @user@host, #hashtag + } else if(acct[0] == '@' && null != acct.find{ it >= 0x80.toChar() } ) { + // @user@host IDNドメインを含む + // 直後に空白を付与する + sb.append("@" + Acct.parse(acct.toString().substring(1)).ascii).append(" ") + }else{ + // @user@host + // #hashtag // 直後に空白を付与する sb.append(acct).append(" ") }