marksmith 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 193c81e84d2038d0008789be16f079c0e71a874d40a2d92dc79ef6c31325fd4b
4
- data.tar.gz: 8b0ee90a5b595712f8e61faa2f35ce50d962df9872cf36705d96315b3b29eac5
3
+ metadata.gz: 6223365f49536a9ee694cd03965e95e7ad4678f1baf8c2d5b8326b4e255f3c14
4
+ data.tar.gz: 300bf849b18a10fd9acedddfcfdff606e207d923d0aa9faa7f29a5e0a5703c46
5
5
  SHA512:
6
- metadata.gz: 86342b9e8ba64a3f5a55eeac8a5a8f0148f3af00d712ee8ffaa1ed0d835743b9cc4f3d67c7f76b7dd2d12062f8681408c66d4213dce5eaa43c440680a3164af7
7
- data.tar.gz: 3e634ebc04720f365256687780479b2fd3d37ff7ff306ea0bfe9dfc8be564b6e3f9b877cd9cbc9d0065ee70ac30ad035b8f1213cba8da4a0a56d7e5df571161d
6
+ metadata.gz: 10a5f5e0597d1a1c4f6119e09919833d56878a94fb96dc6d50e3ca7ebc2396a8f3afdd073f9c06c2b066b185f9d22456d8f85835400b581177a440e3bc6af2cd
7
+ data.tar.gz: c94d8651053d3870af6ba50eabbf9d4147098a47b22e16b2341307d4074edc4118ee0c923cf58ee0eb5c326c9ce8f807d74d5ec92ce6d8f250c89df2ef572951
data/README.md CHANGED
@@ -50,9 +50,9 @@ npm install @avo-hq/marksmith
50
50
  Import and register it in your application.
51
51
 
52
52
  ```js
53
- import Marksmith from '@avo-hq/marksmith'
53
+ import { MarksmithController } from '@avo-hq/marksmith'
54
54
 
55
- application.register('marksmith', Marksmith)
55
+ application.register('marksmith', MarksmithController)
56
56
  ```
57
57
 
58
58
  > [!NOTE]
@@ -61,9 +61,9 @@ application.register('marksmith', Marksmith)
61
61
 
62
62
  ```js
63
63
  // Import just the controller
64
- import Marksmith from '@avo-hq/marksmith/controller'
64
+ import { MarksmithController } from '@avo-hq/marksmith/core'
65
65
 
66
- application.register('marksmith', Marksmith)
66
+ application.register('marksmith', MarksmithController)
67
67
 
68
68
  // Manually import Marksmith's dependencies
69
69
  import '@github/markdown-toolbar-element'
@@ -102,6 +102,20 @@ It supports basic styles for headings, `strong`, `italic` and others.
102
102
 
103
103
  The field supports Actve Storage uploads using drag and drop and pasting files into the field.
104
104
 
105
+ ## List continuation
106
+
107
+ Marksmith has this great opt-in feature where you can have your lists continued.
108
+ We need to add the `ListContinuation` controller too.
109
+
110
+ ```js
111
+ import { ListContinuationController, MarksmithController } from '@avo-hq/marksmith'
112
+ // or /core for the no-dependencies version
113
+ import { ListContinuationController, MarksmithController } from '@avo-hq/marksmith/core'
114
+
115
+ application.register('marksmith', MarksmithController)
116
+ application.register('list-continuation', ListContinuationController)
117
+ ```
118
+
105
119
  ## Contributing
106
120
 
107
121
  Contribution directions go here.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="800px" height="800px" viewBox="0 0 24 24" role="img"><script xmlns="" src="chrome-extension://hoklmmgfnpapgjgcpechhaamimifchmp/frame_ant/frame_ant.js"/><title>Markdown icon</title><path d="M22.269 19.385H1.731a1.73 1.73 0 0 1-1.73-1.73V6.345a1.73 1.73 0 0 1 1.73-1.73h20.538a1.73 1.73 0 0 1 1.73 1.73v11.308a1.73 1.73 0 0 1-1.73 1.731zm-16.5-3.462v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.461v7.847zM21.231 12h-2.308V8.077h-2.307V12h-2.308l3.461 4.039z"/></svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
2
+ <path stroke-linecap="round" stroke-linejoin="round" d="m18.375 12.739-7.693 7.693a4.5 4.5 0 0 1-6.364-6.364l10.94-10.94A3 3 0 1 1 19.5 7.372L8.552 18.32m.009-.01-.01.01m5.699-9.941-7.81 7.81a1.5 1.5 0 0 0 2.112 2.13" />
3
+ </svg>
@@ -1,4 +1,4 @@
1
- /*! tailwindcss v4.0.0-beta.10 | MIT License | https://tailwindcss.com */
1
+ /*! tailwindcss v4.0.0 | MIT License | https://tailwindcss.com */
2
2
  :root {
3
3
  --ms-font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
4
4
  'Segoe UI Symbol', 'Noto Color Emoji';
@@ -801,18 +801,31 @@
801
801
  width: calc(var(--ms-spacing) * 4);
802
802
  height: calc(var(--ms-spacing) * 4);
803
803
  }
804
+ .ms\:size-full {
805
+ width: 100%;
806
+ height: 100%;
807
+ }
804
808
  .ms\:w-full {
805
809
  width: 100%;
806
810
  }
811
+ .ms\:max-w-none {
812
+ max-width: none;
813
+ }
807
814
  .ms\:flex-1 {
808
815
  flex: 1;
809
816
  }
817
+ .ms\:flex-grow {
818
+ flex-grow: 1;
819
+ }
810
820
  .ms\:grow {
811
821
  flex-grow: 1;
812
822
  }
813
823
  .ms\:cursor-pointer {
814
824
  cursor: pointer;
815
825
  }
826
+ .ms\:resize-y {
827
+ resize: vertical;
828
+ }
816
829
  .ms\:flex-col {
817
830
  flex-direction: column;
818
831
  }
@@ -822,9 +835,22 @@
822
835
  .ms\:flex-wrap {
823
836
  flex-wrap: wrap;
824
837
  }
838
+ .ms\:items-center {
839
+ align-items: center;
840
+ }
841
+ .ms\:gap-x-2 {
842
+ column-gap: calc(var(--ms-spacing) * 2);
843
+ }
825
844
  .ms\:gap-y-1 {
826
845
  row-gap: calc(var(--ms-spacing) * 1);
827
846
  }
847
+ .ms\:space-x-2 {
848
+ :where(& > :not(:last-child)) {
849
+ --tw-space-x-reverse: 0;
850
+ margin-inline-start: calc(calc(var(--ms-spacing) * 2) * var(--tw-space-x-reverse));
851
+ margin-inline-end: calc(calc(var(--ms-spacing) * 2) * calc(1 - var(--tw-space-x-reverse)));
852
+ }
853
+ }
828
854
  .ms\:rounded {
829
855
  border-radius: 0.25rem;
830
856
  }
@@ -844,18 +870,27 @@
844
870
  --tw-border-style: none;
845
871
  border-style: none;
846
872
  }
847
- .ms\:border-zinc-300 {
848
- border-color: var(--ms-color-zinc-300);
873
+ .ms\:border-neutral-300 {
874
+ border-color: var(--ms-color-neutral-300);
875
+ }
876
+ .ms\:bg-neutral-50 {
877
+ background-color: var(--ms-color-neutral-50);
849
878
  }
850
- .ms\:bg-zinc-50 {
851
- background-color: var(--ms-color-zinc-50);
879
+ .ms\:bg-neutral-200 {
880
+ background-color: var(--ms-color-neutral-200);
852
881
  }
853
- .ms\:bg-zinc-200 {
854
- background-color: var(--ms-color-zinc-200);
882
+ .ms\:bg-transparent {
883
+ background-color: transparent;
855
884
  }
856
885
  .ms\:bg-none {
857
886
  background-image: none;
858
887
  }
888
+ .ms\:p-2 {
889
+ padding: calc(var(--ms-spacing) * 2);
890
+ }
891
+ .ms\:px-1 {
892
+ padding-inline: calc(var(--ms-spacing) * 1);
893
+ }
859
894
  .ms\:px-1\.5 {
860
895
  padding-inline: calc(var(--ms-spacing) * 1.5);
861
896
  }
@@ -871,56 +906,109 @@
871
906
  .ms\:py-2 {
872
907
  padding-block: calc(var(--ms-spacing) * 2);
873
908
  }
874
- .ms\:prose-zinc {
875
- --tw-prose-body: oklch(0.37 0.013 285.805);
876
- --tw-prose-headings: oklch(0.21 0.006 285.885);
877
- --tw-prose-lead: oklch(0.442 0.017 285.786);
878
- --tw-prose-links: oklch(0.21 0.006 285.885);
879
- --tw-prose-bold: oklch(0.21 0.006 285.885);
880
- --tw-prose-counters: oklch(0.552 0.016 285.938);
881
- --tw-prose-bullets: oklch(0.871 0.006 286.286);
882
- --tw-prose-hr: oklch(0.92 0.004 286.32);
883
- --tw-prose-quotes: oklch(0.21 0.006 285.885);
884
- --tw-prose-quote-borders: oklch(0.92 0.004 286.32);
885
- --tw-prose-captions: oklch(0.552 0.016 285.938);
886
- --tw-prose-kbd: oklch(0.21 0.006 285.885);
909
+ .ms\:py-px {
910
+ padding-block: 1px;
911
+ }
912
+ .ms\:font-mono {
913
+ font-family: var(--ms-font-mono);
914
+ }
915
+ .ms\:font-sans {
916
+ font-family: var(--ms-font-sans);
917
+ }
918
+ .ms\:text-sm {
919
+ font-size: var(--ms-text-sm);
920
+ line-height: var(--tw-leading, var(--ms-text-sm--line-height));
921
+ }
922
+ .ms\:text-xs {
923
+ font-size: var(--ms-text-xs);
924
+ line-height: var(--tw-leading, var(--ms-text-xs--line-height));
925
+ }
926
+ .ms\:font-semibold {
927
+ --tw-font-weight: var(--ms-font-weight-semibold);
928
+ font-weight: var(--ms-font-weight-semibold);
929
+ }
930
+ .ms\:text-neutral-600 {
931
+ color: var(--ms-color-neutral-600);
932
+ }
933
+ .ms\:text-neutral-800 {
934
+ color: var(--ms-color-neutral-800);
935
+ }
936
+ .ms\:uppercase {
937
+ text-transform: uppercase;
938
+ }
939
+ .ms\:no-underline {
940
+ text-decoration-line: none;
941
+ }
942
+ .ms\:prose-neutral {
943
+ --tw-prose-body: oklch(0.371 0 0);
944
+ --tw-prose-headings: oklch(0.205 0 0);
945
+ --tw-prose-lead: oklch(0.439 0 0);
946
+ --tw-prose-links: oklch(0.205 0 0);
947
+ --tw-prose-bold: oklch(0.205 0 0);
948
+ --tw-prose-counters: oklch(0.556 0 0);
949
+ --tw-prose-bullets: oklch(0.87 0 0);
950
+ --tw-prose-hr: oklch(0.922 0 0);
951
+ --tw-prose-quotes: oklch(0.205 0 0);
952
+ --tw-prose-quote-borders: oklch(0.922 0 0);
953
+ --tw-prose-captions: oklch(0.556 0 0);
954
+ --tw-prose-kbd: oklch(0.205 0 0);
887
955
  --tw-prose-kbd-shadows: NaN NaN NaN;
888
- --tw-prose-code: oklch(0.21 0.006 285.885);
889
- --tw-prose-pre-code: oklch(0.92 0.004 286.32);
890
- --tw-prose-pre-bg: oklch(0.274 0.006 286.033);
891
- --tw-prose-th-borders: oklch(0.871 0.006 286.286);
892
- --tw-prose-td-borders: oklch(0.92 0.004 286.32);
893
- --tw-prose-invert-body: oklch(0.871 0.006 286.286);
956
+ --tw-prose-code: oklch(0.205 0 0);
957
+ --tw-prose-pre-code: oklch(0.922 0 0);
958
+ --tw-prose-pre-bg: oklch(0.269 0 0);
959
+ --tw-prose-th-borders: oklch(0.87 0 0);
960
+ --tw-prose-td-borders: oklch(0.922 0 0);
961
+ --tw-prose-invert-body: oklch(0.87 0 0);
894
962
  --tw-prose-invert-headings: #fff;
895
- --tw-prose-invert-lead: oklch(0.705 0.015 286.067);
963
+ --tw-prose-invert-lead: oklch(0.708 0 0);
896
964
  --tw-prose-invert-links: #fff;
897
965
  --tw-prose-invert-bold: #fff;
898
- --tw-prose-invert-counters: oklch(0.705 0.015 286.067);
899
- --tw-prose-invert-bullets: oklch(0.442 0.017 285.786);
900
- --tw-prose-invert-hr: oklch(0.37 0.013 285.805);
901
- --tw-prose-invert-quotes: oklch(0.967 0.001 286.375);
902
- --tw-prose-invert-quote-borders: oklch(0.37 0.013 285.805);
903
- --tw-prose-invert-captions: oklch(0.705 0.015 286.067);
966
+ --tw-prose-invert-counters: oklch(0.708 0 0);
967
+ --tw-prose-invert-bullets: oklch(0.439 0 0);
968
+ --tw-prose-invert-hr: oklch(0.371 0 0);
969
+ --tw-prose-invert-quotes: oklch(0.97 0 0);
970
+ --tw-prose-invert-quote-borders: oklch(0.371 0 0);
971
+ --tw-prose-invert-captions: oklch(0.708 0 0);
904
972
  --tw-prose-invert-kbd: #fff;
905
973
  --tw-prose-invert-kbd-shadows: 255 255 255;
906
974
  --tw-prose-invert-code: #fff;
907
- --tw-prose-invert-pre-code: oklch(0.871 0.006 286.286);
975
+ --tw-prose-invert-pre-code: oklch(0.87 0 0);
908
976
  --tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);
909
- --tw-prose-invert-th-borders: oklch(0.442 0.017 285.786);
910
- --tw-prose-invert-td-borders: oklch(0.37 0.013 285.805);
977
+ --tw-prose-invert-th-borders: oklch(0.439 0 0);
978
+ --tw-prose-invert-td-borders: oklch(0.371 0 0);
911
979
  }
912
- .ms\:focus-within\:border-zinc-500 {
980
+ .ms\:focus-within\:border-neutral-400 {
913
981
  &:focus-within {
914
- border-color: var(--ms-color-zinc-500);
982
+ border-color: var(--ms-color-neutral-400);
915
983
  }
916
984
  }
917
- .ms\:hover\:bg-zinc-200 {
985
+ .ms\:hover\:bg-neutral-100 {
918
986
  &:hover {
919
987
  @media (hover: hover) {
920
- background-color: var(--ms-color-zinc-200);
988
+ background-color: var(--ms-color-neutral-100);
921
989
  }
922
990
  }
923
991
  }
992
+ .ms\:hover\:bg-neutral-200 {
993
+ &:hover {
994
+ @media (hover: hover) {
995
+ background-color: var(--ms-color-neutral-200);
996
+ }
997
+ }
998
+ }
999
+ .ms\:hover\:bg-neutral-300 {
1000
+ &:hover {
1001
+ @media (hover: hover) {
1002
+ background-color: var(--ms-color-neutral-300);
1003
+ }
1004
+ }
1005
+ }
1006
+ .ms\:focus\:ring-0 {
1007
+ &:focus {
1008
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
1009
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1010
+ }
1011
+ }
924
1012
  .ms\:focus\:outline-none {
925
1013
  &:focus {
926
1014
  --tw-outline-style: none;
@@ -932,6 +1020,45 @@
932
1020
  flex-direction: row;
933
1021
  }
934
1022
  }
1023
+ .ms\:button-spinner {
1024
+ width: 24px;
1025
+ height: 24px;
1026
+ position: relative;
1027
+ }
1028
+ .ms\:button-spinner > .double-bounce1, .ms\:button-spinner > .double-bounce2 {
1029
+ width: 100%;
1030
+ height: 100%;
1031
+ border-radius: 50%;
1032
+ background-color: #333;
1033
+ opacity: 0.5;
1034
+ position: absolute;
1035
+ top: 0;
1036
+ left: 0;
1037
+ -webkit-animation: sk-bounce 2s infinite ease-in-out;
1038
+ animation: sk-bounce 2s infinite ease-in-out;
1039
+ }
1040
+ .ms\:button-spinner > .double-bounce2 {
1041
+ -webkit-animation-delay: -1s;
1042
+ animation-delay: -1s;
1043
+ }
1044
+ @-webkit-keyframes sk-bounce {
1045
+ 0%, 100% {
1046
+ -webkit-transform: scale(0);
1047
+ }
1048
+ 50% {
1049
+ -webkit-transform: scale(1);
1050
+ }
1051
+ }
1052
+ @keyframes sk-bounce {
1053
+ 0%, 100% {
1054
+ transform: scale(0);
1055
+ -webkit-transform: scale(0);
1056
+ }
1057
+ 50% {
1058
+ transform: scale(1);
1059
+ -webkit-transform: scale(1);
1060
+ }
1061
+ }
935
1062
  @keyframes spin {
936
1063
  to {
937
1064
  transform: rotate(360deg);
@@ -958,8 +1085,72 @@
958
1085
  animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
959
1086
  }
960
1087
  }
1088
+ @property --tw-space-x-reverse {
1089
+ syntax: "*";
1090
+ inherits: false;
1091
+ initial-value: 0;
1092
+ }
961
1093
  @property --tw-border-style {
962
1094
  syntax: "*";
963
1095
  inherits: false;
964
1096
  initial-value: solid;
965
1097
  }
1098
+ @property --tw-font-weight {
1099
+ syntax: "*";
1100
+ inherits: false;
1101
+ }
1102
+ @property --tw-shadow {
1103
+ syntax: "*";
1104
+ inherits: false;
1105
+ initial-value: 0 0 #0000;
1106
+ }
1107
+ @property --tw-shadow-color {
1108
+ syntax: "*";
1109
+ inherits: false;
1110
+ }
1111
+ @property --tw-inset-shadow {
1112
+ syntax: "*";
1113
+ inherits: false;
1114
+ initial-value: 0 0 #0000;
1115
+ }
1116
+ @property --tw-inset-shadow-color {
1117
+ syntax: "*";
1118
+ inherits: false;
1119
+ }
1120
+ @property --tw-ring-color {
1121
+ syntax: "*";
1122
+ inherits: false;
1123
+ }
1124
+ @property --tw-ring-shadow {
1125
+ syntax: "*";
1126
+ inherits: false;
1127
+ initial-value: 0 0 #0000;
1128
+ }
1129
+ @property --tw-inset-ring-color {
1130
+ syntax: "*";
1131
+ inherits: false;
1132
+ }
1133
+ @property --tw-inset-ring-shadow {
1134
+ syntax: "*";
1135
+ inherits: false;
1136
+ initial-value: 0 0 #0000;
1137
+ }
1138
+ @property --tw-ring-inset {
1139
+ syntax: "*";
1140
+ inherits: false;
1141
+ }
1142
+ @property --tw-ring-offset-width {
1143
+ syntax: "<length>";
1144
+ inherits: false;
1145
+ initial-value: 0px;
1146
+ }
1147
+ @property --tw-ring-offset-color {
1148
+ syntax: "*";
1149
+ inherits: false;
1150
+ initial-value: #fff;
1151
+ }
1152
+ @property --tw-ring-offset-shadow {
1153
+ syntax: "*";
1154
+ inherits: false;
1155
+ initial-value: 0 0 #0000;
1156
+ }
@@ -9,3 +9,54 @@
9
9
  @plugin "@tailwindcss/typography";
10
10
 
11
11
  @source "./../../views/";
12
+ @source "./../../helpers/";
13
+ @source "./../../../lib/";
14
+
15
+ .ms\:button-spinner {
16
+ width: 24px;
17
+ height: 24px;
18
+
19
+ position: relative;
20
+ }
21
+
22
+ .ms\:button-spinner > .double-bounce1,
23
+ .ms\:button-spinner > .double-bounce2 {
24
+ width: 100%;
25
+ height: 100%;
26
+ border-radius: 50%;
27
+ background-color: #333;
28
+ opacity: 0.5;
29
+ position: absolute;
30
+ top: 0;
31
+ left: 0;
32
+
33
+ -webkit-animation: sk-bounce 2s infinite ease-in-out;
34
+ animation: sk-bounce 2s infinite ease-in-out;
35
+ }
36
+
37
+ .ms\:button-spinner > .double-bounce2 {
38
+ -webkit-animation-delay: -1s;
39
+ animation-delay: -1s;
40
+ }
41
+
42
+ @-webkit-keyframes sk-bounce {
43
+ 0%,
44
+ 100% {
45
+ -webkit-transform: scale(0);
46
+ }
47
+ 50% {
48
+ -webkit-transform: scale(1);
49
+ }
50
+ }
51
+
52
+ @keyframes sk-bounce {
53
+ 0%,
54
+ 100% {
55
+ transform: scale(0);
56
+ -webkit-transform: scale(0);
57
+ }
58
+ 50% {
59
+ transform: scale(1);
60
+ -webkit-transform: scale(1);
61
+ }
62
+ }
@@ -0,0 +1,6 @@
1
+ import MarksmithController from "./marksmith_controller"
2
+ import ListContinuationController from "./list_continuation_controller"
3
+
4
+ export { MarksmithController, ListContinuationController }
5
+
6
+
@@ -0,0 +1,94 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.isInsertLineBreak = false
6
+ this.isProcessing = false // Guard flag to prevent recursion
7
+
8
+ this.SPACE_PATTERN = /^(\s*)?/
9
+ this.LIST_PATTERN = /^(\s*)([*-]|(\d+)\.)\s(\[[\sx]\]\s)?/
10
+ }
11
+
12
+ handleBeforeInput(event) {
13
+ if (this.isProcessing) return
14
+ this.isInsertLineBreak = event.inputType === 'insertLineBreak'
15
+ }
16
+
17
+ handleInput(event) {
18
+ if (this.isProcessing) return
19
+ if (this.isInsertLineBreak || event.inputType === 'insertLineBreak') {
20
+ this.handleListContinuation(event.target)
21
+ this.isInsertLineBreak = false
22
+ }
23
+ }
24
+
25
+ handleListContinuation(textarea) {
26
+ if (this.isProcessing) return
27
+
28
+ const result = this.analyzeCurrentLine(
29
+ textarea.value,
30
+ [textarea.selectionStart, textarea.selectionEnd],
31
+ )
32
+
33
+ if (result !== undefined) {
34
+ this.isProcessing = true
35
+ try {
36
+ this.applyTextChange(textarea, result)
37
+ } finally {
38
+ // Ensure we always reset the processing flag
39
+ setTimeout(() => {
40
+ this.isProcessing = false
41
+ }, 0)
42
+ }
43
+ }
44
+ }
45
+
46
+ analyzeCurrentLine(text, [cursorPosition]) {
47
+ if (!cursorPosition || !text) return
48
+
49
+ // Get all lines up to cursor
50
+ const lines = text.substring(0, cursorPosition).split('\n')
51
+ const previousLine = lines[lines.length - 2]
52
+
53
+ // If no previous line or doesn't match list pattern, do nothing
54
+ const match = previousLine?.match(this.LIST_PATTERN)
55
+ if (!match) return
56
+
57
+ const [fullMatch, indentation, listMarker, number, checkbox] = match
58
+
59
+ // Check if previous line was empty (just list marker)
60
+ const previousContent = previousLine.replace(fullMatch, '').trim()
61
+ if (previousContent.length === 0) {
62
+ // Terminate the list by removing the marker
63
+ const start = cursorPosition - `\n${fullMatch}`.length
64
+
65
+ return {
66
+ text: text.substring(0, start) + text.substring(cursorPosition),
67
+ selection: [start, start],
68
+ operation: 'delete',
69
+ }
70
+ }
71
+
72
+ // For numbered lists, increment the number
73
+ const newMarker = number ? `${parseInt(number, 10) + 1}.` : listMarker
74
+
75
+ // Maintain checkbox if it was present
76
+ const prefix = `${indentation}${newMarker} ${checkbox ? '[ ] ' : ''}`
77
+
78
+ // Continue the list with the same indentation and style
79
+ return {
80
+ text: text.substring(0, cursorPosition) + prefix + text.substring(cursorPosition),
81
+ selection: [cursorPosition + prefix.length, cursorPosition + prefix.length],
82
+ operation: 'insert',
83
+ }
84
+ }
85
+
86
+ applyTextChange(textarea, { text, selection }) {
87
+ // Set new value directly
88
+ textarea.value = text
89
+ // Set the cursor position
90
+ const [start, end] = selection
91
+ textarea.selectionStart = start
92
+ textarea.selectionEnd = end
93
+ }
94
+ }
@@ -17,10 +17,10 @@ export default class extends Controller {
17
17
  fieldId: String,
18
18
  }
19
19
 
20
- static targets = ['fieldElement', 'previewElement', 'writeTabButton', 'previewTabButton', 'toolbar']
20
+ static targets = ['fieldContainer', 'fieldElement', 'previewElement', 'writeTabButton', 'previewTabButton', 'toolbar']
21
21
 
22
22
  connect() {
23
- subscribe(this.fieldElementTarget, { defaultPlainTextPaste: { urlLinks: true } })
23
+ subscribe(this.fieldContainerTarget, { defaultPlainTextPaste: { urlLinks: true } })
24
24
  }
25
25
 
26
26
  switchToWrite(event) {
@@ -31,7 +31,7 @@ export default class extends Controller {
31
31
  this.previewTabButtonTarget.classList.remove('ms:hidden')
32
32
 
33
33
  // toggle write/preview buttons
34
- this.fieldElementTarget.classList.remove('ms:hidden')
34
+ this.fieldContainerTarget.classList.remove('ms:hidden')
35
35
  this.previewElementTarget.classList.add('ms:hidden')
36
36
 
37
37
  // toggle the toolbar back
@@ -58,7 +58,7 @@ export default class extends Controller {
58
58
  this.previewTabButtonTarget.classList.add('ms:hidden')
59
59
 
60
60
  // toggle elements
61
- this.fieldElementTarget.classList.add('ms:hidden')
61
+ this.fieldContainerTarget.classList.add('ms:hidden')
62
62
  this.previewElementTarget.classList.remove('ms:hidden')
63
63
 
64
64
  // toggle the toolbar
@@ -77,6 +77,21 @@ export default class extends Controller {
77
77
  this.uploadFiles(event.clipboardData.files)
78
78
  }
79
79
 
80
+ buttonUpload(event) {
81
+ event.preventDefault()
82
+ // Create a hidden file input and trigger it
83
+ const fileInput = document.createElement('input')
84
+ fileInput.type = 'file'
85
+ fileInput.multiple = true
86
+ fileInput.accept = 'image/*,.pdf,.doc,.docx,.txt'
87
+
88
+ fileInput.addEventListener('change', (e) => {
89
+ this.uploadFiles(e.target.files)
90
+ })
91
+
92
+ fileInput.click()
93
+ }
94
+
80
95
  uploadFiles(files) {
81
96
  Array.from(files).forEach((file) => this.uploadFile(file))
82
97
  }
@@ -1,6 +1,6 @@
1
1
  <%= turbo_stream.update params[:element_id] do %>
2
2
  <div class="ms:px-3 ms:py-2">
3
- <%= render "marksmith/shared/rendered_body", body: @body %>
3
+ <%= render partial: "marksmith/shared/rendered_body", locals: { body: @body } %>
4
4
  </div>
5
5
  <% end %>
6
6
 
@@ -6,6 +6,7 @@
6
6
  style = local_assigns[:style] || nil
7
7
  classes = local_assigns[:class] || nil
8
8
  rows = local_assigns[:rows] || 15
9
+ field_name = form&.field_name(name) || name
9
10
  value = if defined?(form)
10
11
  form.object.send(name)
11
12
  else
@@ -14,21 +15,25 @@
14
15
  extra_preview_params = local_assigns[:extra_preview_params] || {}
15
16
  %>
16
17
  <%= content_tag :div,
17
- class: "ms:flex ms:flex-col ms:w-full ms:border ms:border-zinc-300 ms:rounded ms:@container ms:group ms:focus-within:border-zinc-500",
18
+ class: "ms:flex ms:flex-col ms:w-full ms:border ms:border-neutral-300 ms:rounded ms:@container ms:group ms:focus-within:border-neutral-400",
18
19
  data: {
19
- controller: "marksmith",
20
+ controller: "marksmith list-continuation",
21
+ action: "
22
+ beforeinput->list-continuation#handleBeforeInput
23
+ input->list-continuation#handleInput
24
+ ",
20
25
  marksmith_preview_url_value: marksmith.markdown_previews_path,
21
26
  marksmith_active_tab_class: "bg-white",
22
27
  marksmith_attach_url_value: main_app.rails_direct_uploads_url,
23
28
  marksmith_extra_preview_params_value: extra_preview_params.as_json,
24
29
  } do %>
25
- <% toggle_button_classes = class_names(marksmith_button_classes, "ms:border-0 ms:bg-none") %>
26
- <div class="ms:flex-1 ms:flex-col-reverse ms:@md:flex-row ms:grow ms:flex ms:justify-bewteen ms:bg-zinc-50 ms:rounded ms:px-2 ms:py-1 ms:gap-y-1">
30
+ <% toggle_button_classes = class_names(marksmith_button_classes, "ms:bg-neutral-200 ms:border-0 ms:bg-none ms:text-sm ms:hover:bg-neutral-300 ms:uppercase ms:text-xs ms:font-semibold ms:text-neutral-800") %>
31
+ <div class="ms:flex-1 ms:flex-col-reverse ms:@md:flex-row ms:grow ms:flex ms:justify-bewteen ms:bg-neutral-50 ms:rounded ms:px-2 ms:py-1 ms:gap-y-1">
27
32
  <div class="ms:flex-1 ms:flex ms:items:center">
28
- <button class="<%= toggle_button_classes %>" data-action="click->marksmith#switchToPreview" data-marksmith-target="previewTabButton">
33
+ <button class="<%= toggle_button_classes %>" data-action="click->marksmith#switchToPreview" data-marksmith-target="previewTabButton" type="button">
29
34
  <%= t('marksmith.preview').humanize %>
30
35
  </button>
31
- <button class="<%= toggle_button_classes %> ms:hidden ms:bg-zinc-200" data-action="click->marksmith#switchToWrite" data-marksmith-target="writeTabButton">
36
+ <button class="<%= toggle_button_classes %> ms:hidden ms:bg-neutral-200" data-action="click->marksmith#switchToWrite" data-marksmith-target="writeTabButton" type="button">
32
37
  <%= t('marksmith.write').humanize %>
33
38
  </button>
34
39
  </div>
@@ -46,29 +51,39 @@
46
51
  <%= marksmith_toolbar_button "task-list" %>
47
52
  </markdown-toolbar>
48
53
  </div>
49
-
50
- <div class="ms:border-t ms:border-zinc-300 ms:flex">
51
- <%= text_area_tag name, value,
52
- id: name,
53
- class: class_names("ms:flex ms:flex-1 ms:rounded ms:border-none ms:py-2 ms:px-3 ms:focus:outline-none", classes),
54
- rows: rows,
55
- data: {
56
- marksmith_target: "fieldElement",
57
- action: "drop->marksmith#dropUpload paste->marksmith#pasteUpload",
58
- **data_attributes
59
- },
60
- disabled:,
61
- placeholder:,
62
- autofocus:,
63
- style:
64
- %>
54
+ <% toolbar_button_classes = "ms:cursor-pointer ms:hover:bg-neutral-100 ms:px-1 ms:py-px ms:rounded ms:text-sm" %>
55
+ <div class="ms:border-t ms:border-neutral-300 ms:flex">
56
+ <%= content_tag :div, class: "ms:flex ms:flex-col ms:size-full", data: { marksmith_target: "fieldContainer" } do %>
57
+ <%= text_area_tag field_name, value,
58
+ id: name,
59
+ class: class_names("ms:flex ms:flex-1 ms:rounded ms:border-none ms:p-2 ms:resize-y ms:focus:outline-none ms:font-mono ms:focus:ring-0", classes),
60
+ rows: rows,
61
+ data: {
62
+ action: "drop->marksmith#dropUpload paste->marksmith#pasteUpload",
63
+ marksmith_target: "fieldElement",
64
+ **data_attributes
65
+ },
66
+ disabled:,
67
+ placeholder:,
68
+ autofocus:,
69
+ style:
70
+ %>
71
+ <div class="ms:flex ms:w-full ms:flex-1 ms:flex-grow ms:space-x-2 ms:py-1 ms:border-t ms:border-neutral-300 ms:px-2 ms:font-sans ms:text-sm">
72
+ <%= link_to "https://docs.github.com/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax", target: "_blank", class: class_names("ms:flex ms:items-center ms:gap-x-2 ms:text-neutral-800 ms:no-underline", toolbar_button_classes) do %>
73
+ <%= image_tag asset_path("marksmith/svgs/markdown.svg"), class: "ms:inline ms:size-4" %> <%= t("marksmith.markdown_is_supported").humanize %>
74
+ <% end %>
75
+ <%= button_tag data: { action: "click->marksmith#buttonUpload" }, class: class_names("ms:bg-none ms:border-none ms:bg-transparent ms:text-neutral-600 ms:items-center ms:flex", toolbar_button_classes) do %>
76
+ <%= image_tag asset_path("marksmith/svgs/paperclip.svg"), class: "ms:inline ms:size-4" %> <%= t("marksmith.attach_files").humanize %>
77
+ <% end %>
78
+ </div>
79
+ <% end %>
65
80
  <%= content_tag :div,
66
- class: "ms:hidden ms:markdown-preview",
81
+ class: "ms:hidden ms:markdown-preview ms:size-full ms:flex-1 ms:flex ms:size-full",
67
82
  id: "markdown-preview-#{name}",
68
83
  data: {
69
84
  marksmith_target: "previewElement",
70
85
  } do %>
71
- <div class="button-spinner">
86
+ <div class="ms:button-spinner">
72
87
  <div class="double-bounce1"></div>
73
88
  <div class="double-bounce2"></div>
74
89
  </div>
@@ -1,3 +1,3 @@
1
- <%= content_tag :div, class: "ms:prose ms:prose-zinc" do %>
2
- <%= sanitize(@body, tags: %w(table th tr td span) + ActionView::Helpers::SanitizeHelper.sanitizer_vendor.safe_list_sanitizer.allowed_tags.to_a) %>
1
+ <%= content_tag :div, class: "ms:prose ms:prose-neutral ms:max-w-none" do %>
2
+ <%= sanitize(body, tags: %w(table th tr td span) + ActionView::Helpers::SanitizeHelper.sanitizer_vendor.safe_list_sanitizer.allowed_tags.to_a) %>
3
3
  <% end %>
@@ -30,12 +30,14 @@
30
30
 
31
31
  en:
32
32
  marksmith:
33
+ attach_files: attach files
33
34
  bold: bold
34
35
  code: code
35
36
  header: header
36
37
  image: image
37
38
  italic: italic
38
39
  link: link
40
+ markdown_is_supported: Markdown is supported
39
41
  ordered_list: ordered list
40
42
  preview: preview
41
43
  quote: quote
@@ -14,7 +14,7 @@ module Marksmith
14
14
  end
15
15
 
16
16
  def marksmith_button_classes
17
- class_names("ms:flex ms:items:center ms:cursor-pointer ms:py-1 ms:px-1.5 ms:hover:bg-zinc-200 ms:rounded")
17
+ class_names("ms:flex ms:items:center ms:cursor-pointer ms:py-1 ms:px-1.5 ms:hover:bg-neutral-200 ms:rounded")
18
18
  end
19
19
 
20
20
  def marksmith_toolbar_button(name, **kwargs)
@@ -1,3 +1,3 @@
1
1
  module Marksmith
2
- VERSION = "0.0.10"
2
+ VERSION = "0.0.12"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marksmith
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Marin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-22 00:00:00.000000000 Z
11
+ date: 2025-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -58,7 +58,9 @@ files:
58
58
  - app/assets/images/marksmith/svgs/italic.svg
59
59
  - app/assets/images/marksmith/svgs/link copy.svg
60
60
  - app/assets/images/marksmith/svgs/link.svg
61
+ - app/assets/images/marksmith/svgs/markdown.svg
61
62
  - app/assets/images/marksmith/svgs/ordered-list.svg
63
+ - app/assets/images/marksmith/svgs/paperclip.svg
62
64
  - app/assets/images/marksmith/svgs/quote.svg
63
65
  - app/assets/images/marksmith/svgs/task-list.svg
64
66
  - app/assets/images/marksmith/svgs/unordered-list.svg
@@ -69,7 +71,9 @@ files:
69
71
  - app/frontend/entrypoints/application.css
70
72
  - app/frontend/entrypoints/application.js
71
73
  - app/frontend/entrypoints/javascript/controllers/application.js
74
+ - app/frontend/entrypoints/javascript/controllers/export.js
72
75
  - app/frontend/entrypoints/javascript/controllers/index.js
76
+ - app/frontend/entrypoints/javascript/controllers/list_continuation_controller.js
73
77
  - app/frontend/entrypoints/javascript/controllers/marksmith_controller.js
74
78
  - app/helpers/marksmith/application_helper.rb
75
79
  - app/jobs/marksmith/application_job.rb