1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

Feature/code snippet lang selection (#1042)

This commit is contained in:
Evgenii Kozlov 2020-10-30 20:46:44 +03:00 committed by GitHub
parent a46000d384
commit 54fe941d43
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 678 additions and 83 deletions

View file

@ -4,7 +4,7 @@
"function": "#6f42c1",
"number": "#0366d6",
"boolean": "#0366d6",
"operator": "#0366d6",
"operator": "#d73a49",
"string": "#05264c",
"property": "#0366d6"
}

View file

@ -0,0 +1,38 @@
{
"keywords": [
{
"pattern": "\\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\\b",
"color": "#d73a49",
"key": "keyword"
}
],
"operators": [
{
"pattern": "(^|[^.])(?:<<=?|>>>?=?|->|--|\\+\\+|&&|\\|\\||::|[?:~]|[-+*/%&|^!=<>]=?)",
"color": "#d73a49",
"key": "operator"
}
],
"other": [
{
"pattern": "(^|[^\\\\])#.*",
"color": "#6a737d",
"key": "comment"
},
{
"pattern": "(?:\\b(?=\\d)|\\B(?=\\.))(?:0[bo])?(?:(?:\\d|0x[\\da-f])[\\da-f]*\\.?\\d*|\\.\\d+)(?:e[+-]?\\d+)?j?\\b",
"color": "#0366d6",
"key": "number"
},
{
"pattern": "(?:[rub]|rb|br)?(\"|')(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1",
"color": "#05264c",
"key": "string"
},
{
"pattern": "(\\/\\/).*",
"color": "#6a737d",
"key": "comment"
}
]
}

View file

@ -9,7 +9,7 @@
"operators": [
{
"pattern": "[*\\/%^!=]=?|\\+[=+]?|-[=-]?|\\|[=|]?|&(?:=|&|\\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\\.\\.\\.",
"color": "#0366d6",
"color": "#d73a49",
"key": "operator"
}
],

View file

@ -6,12 +6,18 @@
"key": "keyword"
}
],
"operators": [],
"operators": [
{
"pattern": "(^|[^.])(?:<<=?|>>>?=?|->|--|\\+\\+|&&|\\|\\||::|[?:~]|[-+*/%&|^!=<>]=?)",
"color": "#d73a49",
"key": "operator"
}
],
"other": [
{
"pattern": "\\(|\\)|\\[|\\]|:|;|\\.|\\||;|\\&|\\{|\\}",
"color": "#b71c1c",
"key": ""
"pattern": "\\w+(?=\\s*\\()",
"color": "#6f42c1",
"key": "function"
},
{
"pattern": "@\\w+",
@ -19,19 +25,24 @@
"key": "annotation"
},
{
"pattern": "\\b\\d+[\\.]?\\d*([eE]\\-?\\d+)?[lLdDfF]?\\b|\\b0x[a-fA-F\\d]+\\b",
"color": "#f4511e",
"key:": ""
"pattern": "\\b0b[01][01_]*L?\\b|\\b0x[\\da-f_]*\\.?[\\da-f_p+-]+\\b|(?:\\b\\d[\\d_]*\\.?[\\d_]*|\\B\\.\\d[\\d_]*)(?:e[+-]?\\d[\\d_]*)?[dfl]?",
"color": "#0366d6",
"key": "number"
},
{
"pattern": "(\\\"(.*)\\\"|\\\"(.*)\\\")",
"color": "#ff0000",
"key": ""
},
{
"pattern": "\\b[A-Z](?:\\w*[a-z]\\w*)?\\b",
"pattern": "\\b[A-Z]\\w*(?=\\s+\\w+\\s*[;,=())])",
"color": "#6e5494",
"key": "class"
},
{
"pattern": "(?:[rub]|rb|br)?(\"|')(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1",
"color": "#05264c",
"key": "string"
},
{
"pattern": "(\\/\\/).*",
"color": "#6a737d",
"key": "comment"
}
]
}

View file

@ -1,37 +1,48 @@
{
"keywords": [
{
"pattern": "\\w+(?=\\s*\\()",
"color": "#6f42c1",
"key": "function"
},
{
"pattern": "(^|[^.])\\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\\b",
"color": "#d73a49",
"key": "keyword"
}
],
"operators": [],
"operators": [
{
"pattern": "\\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\\/*%<>]=?|[?:]:?|\\.\\.|&&|\\|\\||\\b(?:and|inv|or|shl|shr|ushr|xor)\\b",
"color": "#d73a49",
"key": "operator"
}
],
"other": [
{
"pattern": "@\\w+",
"pattern": "\\w+(?=\\s*\\()",
"color": "#6f42c1",
"key": "function"
},
{
"pattern": "\\B@(?:\\w+:)?(?:[A-Z]\\w*|\\[[^\\]]+\\])",
"color": "#b87333",
"key": "annotation"
},
{
"pattern": "\\b\\d+[\\.]?\\d*([eE]\\-?\\d+)?[lLdDfF]?\\b|\\b0x[a-fA-F\\d]+\\b",
"pattern": "\\b(?:0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\\d+(?:_\\d+)*(?:\\.\\d+(?:_\\d+)*)?(?:[eE][+-]?\\d+(?:_\\d+)*)?[fFL]?)\\b",
"color": "#0366d6",
"key": "number"
},
{
"pattern": "(\\\"(.*)\\\"|\\\"(.*)\\\")",
"color": "#ff0000",
"key": ""
},
{
"pattern": "\\b[A-Z](?:\\w*[a-z]\\w*)?\\b",
"color": "#6e5494",
"key": "class"
},
{
"pattern": "(?:[rub]|rb|br)?(\"|')(?:\\\\.|(?!\\1)[^\\\\\\r\\n])*\\1",
"color": "#05264c",
"key": "string"
},
{
"pattern": "(\\/\\/).*",
"color": "#6a737d",
"key": "comment"
}
]
}

View file

@ -0,0 +1,67 @@
{
"abap": "ABAP",
"arduino": "Arduino",
"bash": "Bash",
"basic": "BASIC",
"c": "C",
"csharp": "C#",
"cpp": "C++",
"clojure": "Clojure",
"coffeescript": "CoffeeScript",
"css": "CSS",
"dart": "Dart",
"diff": "Diff",
"docker": "Docker",
"elixir": "Elixir",
"elm": "Elm",
"erlang": "Erlang",
"flow": "Flow",
"fortran": "Fortran",
"fsharp": "F#",
"gherkin": "Gherkin",
"graphql": "GraphQL",
"groovy": "Groovy",
"go": "Go",
"haskell": "Haskell",
"html": "HTML",
"json": "JSON",
"javascript": "JavaScript",
"java": "Java",
"kotlin": "Kotlin",
"latex": "LaTeX",
"less": "Less",
"lisp": "Lisp",
"livescript": "LiveScript",
"lua": "Lua",
"markup": "Markup",
"markdown": "Markdown",
"makefile": "Makefile",
"matlab": "MATLAB",
"nginx": "Nginx",
"objc": "Objective-C",
"ocaml": "OCaml",
"pascal": "Pascal",
"perl": "Perl",
"php": "PHP",
"powershell": "Power Shell",
"prolog": "Prolog",
"python": "Python",
"reason": "Reason",
"ruby": "Ruby",
"rust": "Rust",
"sass": "Sass",
"scala": "Scala",
"scheme": "Scheme",
"scss": "SСSS",
"shell": "Shell",
"sql": "SQL",
"swift": "Swift",
"typescript": "TypeScript",
"vbnet": "Vb.Net",
"verilog": "Verilog",
"vhdl": "VHDL",
"vb": "Visual Basic",
"wasm": "WebAssembly",
"xml": "XML",
"yaml": "YAML"
}

View file

@ -9,8 +9,8 @@
"operators": [
{
"pattern": "[-+%=]=?|!=|\\*\\*?=?|\\/\\/?=?|<[<=>]?|>[=>]?|[&|^~]",
"color": "#0366d6",
"key": "keyword"
"color": "#d73a49",
"key": "operator"
}
],
"other": [

View file

@ -14,8 +14,8 @@
"operators": [
{
"pattern": "--|\\+\\+|\\*\\*=?|=>|&&=?|\\|\\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\\.{3}|\\?\\?=?|\\?\\.?|[~:]",
"color": "#bd2c00",
"key": "keyword"
"color": "#d73a49",
"key": "operator"
}
],
"other": [

View file

@ -167,6 +167,7 @@ object EditorSessionModule {
updateTitle: UpdateTitle,
updateText: UpdateText,
uploadBlock: UploadBlock,
updateFields: UpdateFields,
updateAlignment: UpdateAlignment,
setupBookmark: SetupBookmark,
turnIntoDocument: TurnIntoDocument,
@ -209,7 +210,8 @@ object EditorSessionModule {
move = move,
paste = paste,
copy = copy,
analytics = analytics
analytics = analytics,
updateFields = updateFields
)
}
@ -520,4 +522,11 @@ object EditorUseCaseModule {
): TurnIntoDocument = TurnIntoDocument(
repo = repo
)
@JvmStatic
@Provides
@PerScreen
fun provideUpdateFieldsUseCase(
repo: BlockRepository
): UpdateFields = UpdateFields(repo)
}

View file

@ -60,6 +60,7 @@ import com.anytypeio.anytype.core_utils.ext.PopupExtensions.calculateRectInWindo
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.block.model.Block.Content.Text
import com.anytypeio.anytype.domain.common.Id
import com.anytypeio.anytype.domain.ext.getFirstLinkMarkupParam
import com.anytypeio.anytype.domain.ext.getSubstring
import com.anytypeio.anytype.emojifier.Emojifier
@ -98,6 +99,7 @@ open class PageFragment :
OnFragmentInteractionListener,
AddBlockFragment.AddBlockActionReceiver,
TurnIntoActionReceiver,
SelectProgrammingLanguageReceiver,
ClipboardInterceptor,
PickiTCallbacks {
@ -730,6 +732,10 @@ open class PageFragment :
is Command.ClearSearchInput -> {
searchToolbar.clear()
}
is Command.Dialog.SelectLanguage -> {
SelectProgrammingLanguageFragment.new(command.target)
.show(childFragmentManager, null)
}
}
}
}
@ -1179,6 +1185,11 @@ open class PageFragment :
vm.navigateToDesktop()
}
override fun onLanguageSelected(target: Id, key: String) {
Timber.d("key: $key")
vm.onSelectProgrammingLanguageClicked(target, key)
}
//------------ End of Anytype Custom Context Menu ------------
companion object {

View file

@ -0,0 +1,74 @@
package com.anytypeio.anytype.ui.page.modals
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.extensions.color
import com.anytypeio.anytype.core_ui.features.page.modal.SelectProgrammingLanguageAdapter
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.domain.common.Id
import com.anytypeio.anytype.library_syntax_highlighter.obtainLanguages
import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.android.synthetic.main.fragment_select_programming_language.*
import timber.log.Timber
class SelectProgrammingLanguageFragment : BaseBottomSheetFragment() {
private val selectLangAdapter by lazy {
SelectProgrammingLanguageAdapter(
items = requireContext().obtainLanguages()
) { lang ->
val parent = parentFragment
check(parent is SelectProgrammingLanguageReceiver)
parent.onLanguageSelected(target, lang)
dismiss()
}
}
private val target: String
get() = requireArguments()
.getString(ARG_TARGET)
?: throw IllegalStateException(MISSING_TARGET_ERROR)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_select_programming_language, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Timber.d("onViewCreated")
dialog?.setOnShowListener { dg ->
val bottomSheet = (dg as? BottomSheetDialog)?.findViewById<FrameLayout>(
com.google.android.material.R.id.design_bottom_sheet
)
bottomSheet?.setBackgroundColor(requireContext().color(android.R.color.transparent))
}
recycler.apply {
layoutManager = LinearLayoutManager(context)
adapter = selectLangAdapter
}
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
companion object {
fun new(target: Id) = SelectProgrammingLanguageFragment().apply {
arguments = bundleOf(ARG_TARGET to target)
}
private const val ARG_TARGET = "arg.select_language.target"
private const val MISSING_TARGET_ERROR = "Target missing in args"
}
}
interface SelectProgrammingLanguageReceiver {
fun onLanguageSelected(target: Id, key: String)
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/TopRoundedCardView"
android:background="@color/white">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.card.MaterialCardView>

View file

@ -9,6 +9,7 @@ import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.updateLayoutParams
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.Focusable
@ -22,11 +23,14 @@ import com.anytypeio.anytype.core_ui.widgets.text.CodeTextInputWidget
import com.anytypeio.anytype.core_ui.widgets.text.EditorLongClickListener
import com.anytypeio.anytype.core_utils.ext.dimen
import com.anytypeio.anytype.core_utils.ext.imm
import com.anytypeio.anytype.library_syntax_highlighter.Syntaxes
import kotlinx.android.synthetic.main.item_block_code_snippet.view.*
import timber.log.Timber
class Code(view: View) : BlockViewHolder(view) {
val menu: TextView
get() = itemView.code_menu
val root: View
get() = itemView
val content: CodeTextInputWidget
@ -92,6 +96,18 @@ class Code(view: View) : BlockViewHolder(view) {
content.setOnClickListener {
onTextInputClicked(item.id)
}
menu.setOnClickListener {
clicked(ListenerType.Code.SelectLanguage(item.id))
}
if (!item.lang.isNullOrEmpty()) {
content.setupSyntax(item.lang)
menu.text = item.lang.capitalize()
} else {
content.setupSyntax(Syntaxes.GENERIC)
menu.setText(R.string.block_code_menu_title)
}
}
fun indentize(item: BlockView.Indentable) {

View file

@ -472,7 +472,8 @@ sealed class BlockView : ViewType, Parcelable {
override val isSelected: Boolean = false,
override val color: String? = null,
override val backgroundColor: String? = null,
override val indent: Int = 0
override val indent: Int = 0,
val lang: String? = null
) : BlockView(), Permission, Selectable, Focusable, Indentable, TextSupport {
override fun getViewType() = HOLDER_CODE_SNIPPET
}

View file

@ -22,21 +22,25 @@ sealed class ListenerType {
data class Error(val target: String) : Picture()
}
sealed class Video: ListenerType() {
sealed class Video : ListenerType() {
data class View(val target: String) : Video()
data class Placeholder(val target: String) : Video()
data class Upload(val target: String) : Video()
data class Error(val target: String) : Video()
}
sealed class Code : ListenerType() {
data class SelectLanguage(val target: String) : Code()
}
data class LongClick(val target: String, val dimensions: BlockDimensions) : ListenerType()
data class EditableBlock(val target: String) : ListenerType()
object TitleBlock : ListenerType()
data class Page(val target: String): ListenerType()
data class Page(val target: String) : ListenerType()
data class Mention(val target: String): ListenerType()
data class Mention(val target: String) : ListenerType()
data class DividerClick(val target: String) : ListenerType()
}

View file

@ -0,0 +1,42 @@
package com.anytypeio.anytype.core_ui.features.page.modal
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.features.page.modal.SelectProgrammingLanguageAdapter.Holder
import kotlinx.android.synthetic.main.item_select_programming_language.view.*
class SelectProgrammingLanguageAdapter(
private val items: List<Pair<String, String>>,
private val onLangSelected: (String) -> Unit
) : RecyclerView.Adapter<Holder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val inflater = LayoutInflater.from(parent.context)
return Holder(
view = inflater.inflate(
R.layout.item_select_programming_language,
parent,
false
)
)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val (key, value) = items[position]
holder.bind(value) { onLangSelected(key) }
}
override fun getItemCount(): Int = items.size
class Holder(view: View) : RecyclerView.ViewHolder(view) {
val lang: TextView = itemView.lang
fun bind(value: String, onClick: () -> Unit) {
lang.text = value
itemView.setOnClickListener { onClick() }
}
}
}

View file

@ -103,4 +103,19 @@ class CodeTextInputWidget : AppCompatEditText, SyntaxHighlighter {
}
super.onSelectionChanged(selStart, selEnd)
}
override fun setupSyntax(lang: String?) {
if (lang == null) {
rules.clear()
clearHighlights()
} else {
val result = context.obtainSyntaxRules(lang)
if (result.isEmpty()) {
addRules(context.obtainGenericSyntaxRules())
} else {
addRules(result)
}
highlight()
}
}
}

View file

@ -5,25 +5,25 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/item_block_code_multi_select_mode_selector"
android:paddingTop="6dp"
android:layout_marginTop="1dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="1dp"
android:paddingBottom="6dp"
android:background="@drawable/item_block_code_multi_select_mode_selector"
android:paddingStart="12dp"
android:paddingEnd="12dp">
android:paddingTop="6dp"
android:paddingEnd="12dp"
android:paddingBottom="6dp">
<LinearLayout
android:id="@+id/snippetContainer"
android:background="@drawable/item_block_code_multi_select_unselected"
android:paddingEnd="20dp"
android:paddingStart="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:layout_marginBottom="1dp"
android:orientation="vertical">
android:background="@drawable/item_block_code_multi_select_unselected"
android:orientation="vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp">
<TextView
android:id="@+id/code_menu"
@ -33,16 +33,23 @@
android:layout_marginTop="@dimen/dp_12"
android:text="@string/block_code_menu_title" />
<com.anytypeio.anytype.core_ui.widgets.text.CodeTextInputWidget
android:id="@+id/snippet"
style="@style/BlockCodeContentStyle"
<HorizontalScrollView
android:scrollbars="none"
android:overScrollMode="never"
android:id="@+id/scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:paddingTop="23dp"
android:paddingBottom="30dp"
android:text="No content yet"
tools:text="@string/default_text_placeholder" />
android:layout_height="wrap_content">
<com.anytypeio.anytype.core_ui.widgets.text.CodeTextInputWidget
android:id="@+id/snippet"
style="@style/BlockCodeContentStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:paddingTop="23dp"
android:paddingBottom="30dp"
tools:text="@string/default_text_placeholder" />
</HorizontalScrollView>
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp">
<TextView
android:id="@+id/lang"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:fontFamily="@font/inter_regular"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textColor="@color/black"
android:textSize="17sp" />
</FrameLayout>

View file

@ -168,4 +168,9 @@ class CommandEntity {
val targets: List<String>,
val style: BlockEntity.Content.Divider.Style
)
data class SetFields(
val context: String,
val fields: List<Pair<String, BlockEntity.Fields>>
)
}

View file

@ -3,6 +3,8 @@ package com.anytypeio.anytype.data.auth.repo.block
import com.anytypeio.anytype.data.auth.exception.BackwardCompatilityNotSupportedException
import com.anytypeio.anytype.data.auth.mapper.toDomain
import com.anytypeio.anytype.data.auth.mapper.toEntity
import com.anytypeio.anytype.data.auth.model.BlockEntity
import com.anytypeio.anytype.data.auth.model.CommandEntity
import com.anytypeio.anytype.data.auth.model.PositionEntity
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.block.model.Command
@ -34,7 +36,8 @@ class BlockDataRepository(
Result.Failure(Error.BackwardCompatibility)
}
override suspend fun openProfile(id: String): Payload = factory.remote.openProfile(id).toDomain()
override suspend fun openProfile(id: String): Payload =
factory.remote.openProfile(id).toDomain()
override suspend fun closeDashboard(id: String) {
factory.remote.closeDashboard(id)
@ -42,7 +45,7 @@ class BlockDataRepository(
override suspend fun updateAlignment(
command: Command.UpdateAlignment
) : Payload = factory.remote.updateAlignment(command.toEntity()).toDomain()
): Payload = factory.remote.updateAlignment(command.toEntity()).toDomain()
override suspend fun createPage(parentId: String, emoji: String?) =
factory.remote.createPage(parentId, emoji)
@ -61,7 +64,7 @@ class BlockDataRepository(
override suspend fun updateTextStyle(
command: Command.UpdateStyle
) : Payload = factory.remote.updateTextStyle(command.toEntity()).toDomain()
): Payload = factory.remote.updateTextStyle(command.toEntity()).toDomain()
override suspend fun updateTextColor(
command: Command.UpdateTextColor
@ -144,11 +147,11 @@ class BlockDataRepository(
override suspend fun undo(
command: Command.Undo
) : Payload = factory.remote.undo(command.toEntity()).toDomain()
): Payload = factory.remote.undo(command.toEntity()).toDomain()
override suspend fun redo(
command: Command.Redo
) : Payload = factory.remote.redo(command.toEntity()).toDomain()
): Payload = factory.remote.redo(command.toEntity()).toDomain()
override suspend fun archiveDocument(
command: Command.ArchiveDocument
@ -190,6 +193,18 @@ class BlockDataRepository(
position = PositionEntity.valueOf(position.name)
).toDomain()
override suspend fun updateDivider(command: Command.UpdateDivider): Payload =
factory.remote.updateDivider(command = command.toEntity()).toDomain()
override suspend fun updateDivider(
command: Command.UpdateDivider
): Payload = factory.remote.updateDivider(command = command.toEntity()).toDomain()
override suspend fun setFields(
command: Command.SetFields
): Payload = factory.remote.setFields(
command = CommandEntity.SetFields(
context = command.context,
fields = command.fields.map { (id, fields) ->
id to BlockEntity.Fields(fields.map.toMutableMap())
}
)
).toDomain()
}

View file

@ -56,5 +56,7 @@ interface BlockDataStore {
position: PositionEntity
): PayloadEntity
suspend fun updateDivider(command: CommandEntity.UpdateDivider) : PayloadEntity
suspend fun updateDivider(command: CommandEntity.UpdateDivider): PayloadEntity
suspend fun setFields(command: CommandEntity.SetFields): PayloadEntity
}

View file

@ -56,5 +56,7 @@ interface BlockRemote {
position: PositionEntity
): PayloadEntity
suspend fun updateDivider(command: CommandEntity.UpdateDivider) : PayloadEntity
suspend fun updateDivider(command: CommandEntity.UpdateDivider): PayloadEntity
suspend fun setFields(command: CommandEntity.SetFields): PayloadEntity
}

View file

@ -148,6 +148,11 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
position = position
)
override suspend fun updateDivider(command: CommandEntity.UpdateDivider): PayloadEntity =
remote.updateDivider(command)
override suspend fun updateDivider(
command: CommandEntity.UpdateDivider
): PayloadEntity = remote.updateDivider(command)
override suspend fun setFields(
command: CommandEntity.SetFields
): PayloadEntity = remote.setFields(command)
}

View file

@ -0,0 +1,26 @@
package com.anytypeio.anytype.domain.block.interactor
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.block.model.Command
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.common.Id
import com.anytypeio.anytype.domain.event.model.Payload
class UpdateFields(private val repo: BlockRepository) :
BaseUseCase<Payload, UpdateFields.Params>() {
override suspend fun run(params: Params) = safe {
repo.setFields(
command = Command.SetFields(
context = params.context,
fields = params.fields
)
)
}
data class Params(
val context: Id,
val fields: List<Pair<Id, Block.Fields>>
)
}

View file

@ -32,6 +32,7 @@ data class Block(
val iconEmoji: String? by default
val iconImage: String? by default
val isArchived: Boolean? by default
val lang: String? by default
companion object {
fun empty(): Fields = Fields(emptyMap())

View file

@ -317,4 +317,9 @@ sealed class Command {
val targets: List<Id>,
val style: Block.Content.Divider.Style
)
data class SetFields(
val context: Id,
val fields: List<Pair<Id, Block.Fields>>
)
}

View file

@ -112,4 +112,6 @@ interface BlockRepository {
): Payload
suspend fun updateDivider(command: Command.UpdateDivider): Payload
suspend fun setFields(command: Command.SetFields): Payload
}

View file

@ -12,6 +12,8 @@ interface SyntaxHighlighter {
val source: Editable
val rules: MutableList<Syntax>
fun setupSyntax(lang: String?)
fun addRules(new: List<Syntax>) {
rules.apply {
clear()
@ -20,7 +22,7 @@ interface SyntaxHighlighter {
}
fun highlight() {
clear()
clearHighlights()
rules.forEach { syntax ->
val matcher = syntax.matcher(source.toString())
while (matcher.find()) {
@ -34,7 +36,7 @@ interface SyntaxHighlighter {
}
}
fun clear() {
fun clearHighlights() {
val current = source.getSpans(0, source.length, SyntaxColorSpan::class.java)
current.forEach { span -> source.removeSpan(span) }
}

View file

@ -3,4 +3,5 @@ package com.anytypeio.anytype.library_syntax_highlighter
object Syntaxes {
const val KOTLIN = "kotlin"
const val PYTHON = "python"
const val GENERIC = "generic"
}

View file

@ -4,19 +4,36 @@ import android.content.Context
import android.graphics.Color
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import java.io.IOException
fun Context.obtainSyntaxRules(language: String): List<Syntax> {
val path = "syntax/${language}.json"
val json = obtainJsonDataFromAsset(path)
checkNotNull(json) { "Could not deserialize syntax rules from path: $path" }
val descriptor = Json.decodeFromString<SyntaxDescriptor>(json)
val rules = descriptor.let { it.keywords + it.operators + it.other }
return rules.map { s ->
Syntax(
regex = s.pattern,
color = Color.parseColor(s.color)
)
return if (json != null) {
val descriptor = Json.decodeFromString<SyntaxDescriptor>(json)
val rules = descriptor.let { it.keywords + it.operators + it.other }
rules.map { s ->
Syntax(
regex = s.pattern,
color = Color.parseColor(s.color)
)
}
} else {
emptyList()
}
}
fun Context.obtainGenericSyntaxRules(): List<Syntax> {
return obtainSyntaxRules(Syntaxes.GENERIC)
}
fun Context.obtainLanguages(): List<Pair<String, String>> {
val json = obtainJsonDataFromAsset("syntax/languages.json")
checkNotNull(json) { "Json data for languages is missing" }
return Json.parseToJsonElement(json).jsonObject.map { (key, element) ->
key to element.jsonPrimitive.content
}
}

View file

@ -165,6 +165,11 @@ class BlockMiddleware(
position: PositionEntity
): PayloadEntity = middleware.linkToObject(context, target, block, replace, position)
override suspend fun updateDivider(command: CommandEntity.UpdateDivider): PayloadEntity =
middleware.updateDividerStyle(command)
override suspend fun updateDivider(
command: CommandEntity.UpdateDivider
): PayloadEntity = middleware.updateDividerStyle(command)
override suspend fun setFields(
command: CommandEntity.SetFields
): PayloadEntity = middleware.setFields(command)
}

View file

@ -14,6 +14,7 @@ import com.anytypeio.anytype.middleware.service.MiddlewareService;
import com.google.protobuf.Struct;
import com.google.protobuf.Value;
import java.util.ArrayList;
import java.util.List;
import anytype.Commands;
@ -1193,4 +1194,36 @@ public class Middleware {
return mapper.toPayload(response.getEvent());
}
public PayloadEntity setFields(CommandEntity.SetFields command) throws Exception {
List<BlockList.Set.Fields.Request.BlockField> fields = new ArrayList<>();
for (int i = 0; i < command.getFields().size(); i++) {
Pair<String, BlockEntity.Fields> item = command.getFields().get(i);
BlockList.Set.Fields.Request.BlockField field = BlockList.Set.Fields.Request.BlockField
.newBuilder()
.setBlockId(item.getFirst())
.setFields(mapper.toMiddleware(item.getSecond()))
.build();
fields.add(field);
}
BlockList.Set.Fields.Request request = BlockList.Set.Fields.Request.newBuilder()
.setContextId(command.getContext())
.addAllBlockFields(fields)
.build();
if (BuildConfig.DEBUG) {
Timber.d(request.getClass().getName() + "\n" + request.toString());
}
BlockList.Set.Fields.Response response = service.blockListSetFields(request);
if (BuildConfig.DEBUG) {
Timber.d(response.getClass().getName() + "\n" + response.toString());
}
return mapper.toPayload(response.getEvent());
}
}

View file

@ -8,7 +8,9 @@ import com.anytypeio.anytype.data.auth.model.PayloadEntity
import com.anytypeio.anytype.data.auth.model.PositionEntity
import com.anytypeio.anytype.middleware.converters.block
import com.anytypeio.anytype.middleware.converters.blocks
import com.anytypeio.anytype.middleware.converters.fields
import com.anytypeio.anytype.middleware.converters.toMiddleware
import com.google.protobuf.Struct
class MiddlewareMapper {
@ -46,7 +48,11 @@ class MiddlewareMapper {
return alignment.toMiddleware()
}
fun toEntity(blocks: List<Block>) : List<BlockEntity> {
fun toEntity(blocks: List<Block>): List<BlockEntity> {
return blocks.blocks()
}
fun toMiddleware(fields: BlockEntity.Fields): Struct {
return fields.fields()
}
}

View file

@ -424,4 +424,15 @@ public class DefaultMiddlewareService implements MiddlewareService {
return response;
}
}
@Override
public BlockList.Set.Fields.Response blockListSetFields(BlockList.Set.Fields.Request request) throws Exception {
byte[] encoded = Service.blockListSetFields(request.toByteArray());
BlockList.Set.Fields.Response response = BlockList.Set.Fields.Response.parseFrom(encoded);
if (response.getError() != null && response.getError().getCode() != BlockList.Set.Fields.Response.Error.Code.NULL) {
throw new Exception(response.getError().getDescription());
} else {
return response;
}
}
}

View file

@ -86,4 +86,6 @@ public interface MiddlewareService {
Commands.Rpc.Page.Create.Response pageCreate(Commands.Rpc.Page.Create.Request request) throws Exception;
Commands.Rpc.Version.Get.Response getVersion(Commands.Rpc.Version.Get.Request request) throws Exception;
BlockList.Set.Fields.Response blockListSetFields(BlockList.Set.Fields.Request request) throws Exception;
}

View file

@ -587,4 +587,70 @@ class MiddlewareTest {
verify(service, times(1)).uploadFile(request)
verifyNoMoreInteractions(service)
}
@Test
fun `should create request for setting block fields`() {
// SETUP
val ctx = MockDataFactory.randomUuid()
val block1 = MockDataFactory.randomUuid()
val block2 = MockDataFactory.randomUuid()
val command = CommandEntity.SetFields(
context = ctx,
fields = listOf(
Pair(
block1,
BlockEntity.Fields(
map = mutableMapOf(
"lang" to "kotlin"
)
)
),
Pair(
block2,
BlockEntity.Fields(
map = mutableMapOf(
"lang" to "python"
)
)
)
)
)
val fields = listOf(
BlockList.Set.Fields.Request.BlockField.newBuilder()
.setBlockId(block1)
.setFields(
Struct.newBuilder()
.putFields("lang", Value.newBuilder().setStringValue("kotlin").build())
)
.build(),
BlockList.Set.Fields.Request.BlockField.newBuilder()
.setBlockId(block2)
.setFields(
Struct.newBuilder()
.putFields("lang", Value.newBuilder().setStringValue("python").build())
)
.build()
)
val request = BlockList.Set.Fields.Request.newBuilder()
.setContextId(ctx)
.addAllBlockFields(fields)
.build()
service.stub {
on { blockListSetFields(request) } doReturn BlockList.Set.Fields.Response.getDefaultInstance()
}
// TESTING
middleware.setFields(command)
verify(service, times(1)).blockListSetFields(request)
verifyNoMoreInteractions(service)
}
}

View file

@ -2632,6 +2632,12 @@ class PageViewModel(
else -> Unit
}
}
is ListenerType.Code.SelectLanguage -> {
when (mode) {
EditorMode.EDITING -> dispatch(Command.Dialog.SelectLanguage(clicked.target))
else -> Unit
}
}
}
fun onPlusButtonPressed() {
@ -2924,6 +2930,24 @@ class PageViewModel(
} ?: run { false }
}
fun onSelectProgrammingLanguageClicked(target: Id, key: String) {
viewModelScope.launch {
orchestrator.proxies.intents.send(
Intent.CRUD.UpdateFields(
context = context,
fields = listOf(
Pair(
target,
Block.Fields(
mapOf("lang" to key)
)
)
)
)
)
}
}
companion object {
const val NO_SEARCH_RESULT_POSITION = -1
const val EMPTY_TEXT = ""

View file

@ -76,4 +76,8 @@ sealed class Command {
val target: Id,
val url: Url
) : Command()
sealed class Dialog : Command() {
data class SelectLanguage(val target: String) : Dialog()
}
}

View file

@ -63,6 +63,11 @@ sealed class Intent {
val next: Id?,
val effects: List<SideEffect> = emptyList()
) : CRUD()
class UpdateFields(
val context: Id,
val fields: List<Pair<Id, Block.Fields>>
) : CRUD()
}
sealed class Clipboard : Intent() {

View file

@ -64,6 +64,7 @@ class Orchestrator(
private val uploadBlock: UploadBlock,
private val setupBookmark: SetupBookmark,
private val turnIntoDocument: TurnIntoDocument,
private val updateFields: UpdateFields,
private val move: Move,
private val copy: Copy,
private val paste: Paste,
@ -201,6 +202,17 @@ class Orchestrator(
}
)
}
is Intent.CRUD.UpdateFields -> {
updateFields(
params = UpdateFields.Params(
context = intent.context,
fields = intent.fields
)
).proceed(
failure = {},
success = { proxies.payloads.send(it) }
)
}
is Intent.Text.Split -> {
val startTime = System.currentTimeMillis()
splitBlock(

View file

@ -477,7 +477,8 @@ class DefaultBlockViewRenderer(
backgroundColor = content.backgroundColor,
color = content.color,
isFocused = block.id == focus.id,
indent = indent
indent = indent,
lang = block.fields.lang
)
private fun highlight(

View file

@ -174,7 +174,10 @@ open class PageViewModelTest {
lateinit var turnIntoDocument: TurnIntoDocument
@Mock
lateinit var gateway : Gateway
lateinit var updateFields: UpdateFields
@Mock
lateinit var gateway: Gateway
@Mock
lateinit var analytics: Analytics
@ -3857,7 +3860,8 @@ open class PageViewModelTest {
copy = copy,
move = move,
turnIntoDocument = turnIntoDocument,
analytics = analytics
analytics = analytics,
updateFields = updateFields
),
bridge = Bridge()
)

View file

@ -105,6 +105,9 @@ open class EditorPresentationTestSetup {
@Mock
lateinit var uploadBlock: UploadBlock
@Mock
lateinit var updateFields: UpdateFields
@Mock
lateinit var paste: Paste
@ -205,7 +208,8 @@ open class EditorPresentationTestSetup {
copy = copy,
move = move,
turnIntoDocument = turnIntoDocument,
analytics = analytics
analytics = analytics,
updateFields = updateFields
),
bridge = Bridge()
)