marksmith 0.0.11 → 0.0.13

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: 5eb26aeaa24188df02fdfaa2f3eccaecc28feac32420ff31e34328359b7939c8
4
- data.tar.gz: e5ae028f87a6c7bceeea0ac89269e320422d1466dd3b3de17d1c7dade9c49b0d
3
+ metadata.gz: '0128324daeb57601c2ba3ab60de880179bb6df7dfcf4e5ead34aee95d35aeb0b'
4
+ data.tar.gz: 0cee7e267e53b84330fbad9b9246a5bafb67234f84a232007f199227ad72358d
5
5
  SHA512:
6
- metadata.gz: 578c3ed2d9ee8c3d58499f5c08edd8b469166f45f98c7b746912a95c04e780b5c310610ec6b83330253e924706827e9c6ae633fadd2f9e7347b2d1f084f0f017
7
- data.tar.gz: 9c4f92ce4997de77e065a9906994dbb4eb611f79648fa4acefcf61f83a02dded7c6a9b9203815f72dbedfb824ad38c9824f79004641133a1352554db5263d5e1
6
+ metadata.gz: 34dd3cee675295dae2662bd4d7cedf5419b2d291b71fcbfc836cc4c18a7de63dd06493f1b0bbfb6c600a24dc528599ec04e890458d1f2c0c725cf8fdc4898e30
7
+ data.tar.gz: 7557d549a8700268931952c383780579823c146f160bb3ebd3de10cb55e5df795342ee1813c3172e448c6f7fea32b962ed50a2107b3016400413fbafbbe461ff
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';
@@ -788,6 +788,9 @@
788
788
  margin-bottom: 0;
789
789
  }
790
790
  }
791
+ .ms\:block {
792
+ display: block;
793
+ }
791
794
  .ms\:flex {
792
795
  display: flex;
793
796
  }
@@ -801,18 +804,31 @@
801
804
  width: calc(var(--ms-spacing) * 4);
802
805
  height: calc(var(--ms-spacing) * 4);
803
806
  }
807
+ .ms\:size-full {
808
+ width: 100%;
809
+ height: 100%;
810
+ }
804
811
  .ms\:w-full {
805
812
  width: 100%;
806
813
  }
814
+ .ms\:max-w-none {
815
+ max-width: none;
816
+ }
807
817
  .ms\:flex-1 {
808
818
  flex: 1;
809
819
  }
820
+ .ms\:flex-grow {
821
+ flex-grow: 1;
822
+ }
810
823
  .ms\:grow {
811
824
  flex-grow: 1;
812
825
  }
813
826
  .ms\:cursor-pointer {
814
827
  cursor: pointer;
815
828
  }
829
+ .ms\:resize-y {
830
+ resize: vertical;
831
+ }
816
832
  .ms\:flex-col {
817
833
  flex-direction: column;
818
834
  }
@@ -822,9 +838,22 @@
822
838
  .ms\:flex-wrap {
823
839
  flex-wrap: wrap;
824
840
  }
841
+ .ms\:items-center {
842
+ align-items: center;
843
+ }
844
+ .ms\:gap-x-2 {
845
+ column-gap: calc(var(--ms-spacing) * 2);
846
+ }
825
847
  .ms\:gap-y-1 {
826
848
  row-gap: calc(var(--ms-spacing) * 1);
827
849
  }
850
+ .ms\:space-x-2 {
851
+ :where(& > :not(:last-child)) {
852
+ --tw-space-x-reverse: 0;
853
+ margin-inline-start: calc(calc(var(--ms-spacing) * 2) * var(--tw-space-x-reverse));
854
+ margin-inline-end: calc(calc(var(--ms-spacing) * 2) * calc(1 - var(--tw-space-x-reverse)));
855
+ }
856
+ }
828
857
  .ms\:rounded {
829
858
  border-radius: 0.25rem;
830
859
  }
@@ -844,18 +873,27 @@
844
873
  --tw-border-style: none;
845
874
  border-style: none;
846
875
  }
847
- .ms\:border-zinc-300 {
848
- border-color: var(--ms-color-zinc-300);
876
+ .ms\:border-neutral-300 {
877
+ border-color: var(--ms-color-neutral-300);
878
+ }
879
+ .ms\:bg-neutral-50 {
880
+ background-color: var(--ms-color-neutral-50);
849
881
  }
850
- .ms\:bg-zinc-50 {
851
- background-color: var(--ms-color-zinc-50);
882
+ .ms\:bg-neutral-200 {
883
+ background-color: var(--ms-color-neutral-200);
852
884
  }
853
- .ms\:bg-zinc-200 {
854
- background-color: var(--ms-color-zinc-200);
885
+ .ms\:bg-transparent {
886
+ background-color: transparent;
855
887
  }
856
888
  .ms\:bg-none {
857
889
  background-image: none;
858
890
  }
891
+ .ms\:p-2 {
892
+ padding: calc(var(--ms-spacing) * 2);
893
+ }
894
+ .ms\:px-1 {
895
+ padding-inline: calc(var(--ms-spacing) * 1);
896
+ }
859
897
  .ms\:px-1\.5 {
860
898
  padding-inline: calc(var(--ms-spacing) * 1.5);
861
899
  }
@@ -871,56 +909,113 @@
871
909
  .ms\:py-2 {
872
910
  padding-block: calc(var(--ms-spacing) * 2);
873
911
  }
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);
912
+ .ms\:py-px {
913
+ padding-block: 1px;
914
+ }
915
+ .ms\:font-mono {
916
+ font-family: var(--ms-font-mono);
917
+ }
918
+ .ms\:font-sans {
919
+ font-family: var(--ms-font-sans);
920
+ }
921
+ .ms\:text-sm {
922
+ font-size: var(--ms-text-sm);
923
+ line-height: var(--tw-leading, var(--ms-text-sm--line-height));
924
+ }
925
+ .ms\:text-xs {
926
+ font-size: var(--ms-text-xs);
927
+ line-height: var(--tw-leading, var(--ms-text-xs--line-height));
928
+ }
929
+ .ms\:leading-normal {
930
+ --tw-leading: var(--ms-leading-normal);
931
+ line-height: var(--ms-leading-normal);
932
+ }
933
+ .ms\:font-semibold {
934
+ --tw-font-weight: var(--ms-font-weight-semibold);
935
+ font-weight: var(--ms-font-weight-semibold);
936
+ }
937
+ .ms\:text-neutral-600 {
938
+ color: var(--ms-color-neutral-600);
939
+ }
940
+ .ms\:text-neutral-800 {
941
+ color: var(--ms-color-neutral-800);
942
+ }
943
+ .ms\:uppercase {
944
+ text-transform: uppercase;
945
+ }
946
+ .ms\:no-underline {
947
+ text-decoration-line: none;
948
+ }
949
+ .ms\:prose-neutral {
950
+ --tw-prose-body: oklch(0.371 0 0);
951
+ --tw-prose-headings: oklch(0.205 0 0);
952
+ --tw-prose-lead: oklch(0.439 0 0);
953
+ --tw-prose-links: oklch(0.205 0 0);
954
+ --tw-prose-bold: oklch(0.205 0 0);
955
+ --tw-prose-counters: oklch(0.556 0 0);
956
+ --tw-prose-bullets: oklch(0.87 0 0);
957
+ --tw-prose-hr: oklch(0.922 0 0);
958
+ --tw-prose-quotes: oklch(0.205 0 0);
959
+ --tw-prose-quote-borders: oklch(0.922 0 0);
960
+ --tw-prose-captions: oklch(0.556 0 0);
961
+ --tw-prose-kbd: oklch(0.205 0 0);
887
962
  --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);
963
+ --tw-prose-code: oklch(0.205 0 0);
964
+ --tw-prose-pre-code: oklch(0.922 0 0);
965
+ --tw-prose-pre-bg: oklch(0.269 0 0);
966
+ --tw-prose-th-borders: oklch(0.87 0 0);
967
+ --tw-prose-td-borders: oklch(0.922 0 0);
968
+ --tw-prose-invert-body: oklch(0.87 0 0);
894
969
  --tw-prose-invert-headings: #fff;
895
- --tw-prose-invert-lead: oklch(0.705 0.015 286.067);
970
+ --tw-prose-invert-lead: oklch(0.708 0 0);
896
971
  --tw-prose-invert-links: #fff;
897
972
  --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);
973
+ --tw-prose-invert-counters: oklch(0.708 0 0);
974
+ --tw-prose-invert-bullets: oklch(0.439 0 0);
975
+ --tw-prose-invert-hr: oklch(0.371 0 0);
976
+ --tw-prose-invert-quotes: oklch(0.97 0 0);
977
+ --tw-prose-invert-quote-borders: oklch(0.371 0 0);
978
+ --tw-prose-invert-captions: oklch(0.708 0 0);
904
979
  --tw-prose-invert-kbd: #fff;
905
980
  --tw-prose-invert-kbd-shadows: 255 255 255;
906
981
  --tw-prose-invert-code: #fff;
907
- --tw-prose-invert-pre-code: oklch(0.871 0.006 286.286);
982
+ --tw-prose-invert-pre-code: oklch(0.87 0 0);
908
983
  --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);
984
+ --tw-prose-invert-th-borders: oklch(0.439 0 0);
985
+ --tw-prose-invert-td-borders: oklch(0.371 0 0);
911
986
  }
912
- .ms\:focus-within\:border-zinc-500 {
987
+ .ms\:focus-within\:border-neutral-400 {
913
988
  &:focus-within {
914
- border-color: var(--ms-color-zinc-500);
989
+ border-color: var(--ms-color-neutral-400);
990
+ }
991
+ }
992
+ .ms\:hover\:bg-neutral-100 {
993
+ &:hover {
994
+ @media (hover: hover) {
995
+ background-color: var(--ms-color-neutral-100);
996
+ }
915
997
  }
916
998
  }
917
- .ms\:hover\:bg-zinc-200 {
999
+ .ms\:hover\:bg-neutral-200 {
918
1000
  &:hover {
919
1001
  @media (hover: hover) {
920
- background-color: var(--ms-color-zinc-200);
1002
+ background-color: var(--ms-color-neutral-200);
921
1003
  }
922
1004
  }
923
1005
  }
1006
+ .ms\:hover\:bg-neutral-300 {
1007
+ &:hover {
1008
+ @media (hover: hover) {
1009
+ background-color: var(--ms-color-neutral-300);
1010
+ }
1011
+ }
1012
+ }
1013
+ .ms\:focus\:ring-0 {
1014
+ &:focus {
1015
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
1016
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1017
+ }
1018
+ }
924
1019
  .ms\:focus\:outline-none {
925
1020
  &:focus {
926
1021
  --tw-outline-style: none;
@@ -932,6 +1027,45 @@
932
1027
  flex-direction: row;
933
1028
  }
934
1029
  }
1030
+ .ms\:button-spinner {
1031
+ width: 24px;
1032
+ height: 24px;
1033
+ position: relative;
1034
+ }
1035
+ .ms\:button-spinner > .double-bounce1, .ms\:button-spinner > .double-bounce2 {
1036
+ width: 100%;
1037
+ height: 100%;
1038
+ border-radius: 50%;
1039
+ background-color: #333;
1040
+ opacity: 0.5;
1041
+ position: absolute;
1042
+ top: 0;
1043
+ left: 0;
1044
+ -webkit-animation: sk-bounce 2s infinite ease-in-out;
1045
+ animation: sk-bounce 2s infinite ease-in-out;
1046
+ }
1047
+ .ms\:button-spinner > .double-bounce2 {
1048
+ -webkit-animation-delay: -1s;
1049
+ animation-delay: -1s;
1050
+ }
1051
+ @-webkit-keyframes sk-bounce {
1052
+ 0%, 100% {
1053
+ -webkit-transform: scale(0);
1054
+ }
1055
+ 50% {
1056
+ -webkit-transform: scale(1);
1057
+ }
1058
+ }
1059
+ @keyframes sk-bounce {
1060
+ 0%, 100% {
1061
+ transform: scale(0);
1062
+ -webkit-transform: scale(0);
1063
+ }
1064
+ 50% {
1065
+ transform: scale(1);
1066
+ -webkit-transform: scale(1);
1067
+ }
1068
+ }
935
1069
  @keyframes spin {
936
1070
  to {
937
1071
  transform: rotate(360deg);
@@ -958,8 +1092,76 @@
958
1092
  animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
959
1093
  }
960
1094
  }
1095
+ @property --tw-space-x-reverse {
1096
+ syntax: "*";
1097
+ inherits: false;
1098
+ initial-value: 0;
1099
+ }
961
1100
  @property --tw-border-style {
962
1101
  syntax: "*";
963
1102
  inherits: false;
964
1103
  initial-value: solid;
965
1104
  }
1105
+ @property --tw-leading {
1106
+ syntax: "*";
1107
+ inherits: false;
1108
+ }
1109
+ @property --tw-font-weight {
1110
+ syntax: "*";
1111
+ inherits: false;
1112
+ }
1113
+ @property --tw-shadow {
1114
+ syntax: "*";
1115
+ inherits: false;
1116
+ initial-value: 0 0 #0000;
1117
+ }
1118
+ @property --tw-shadow-color {
1119
+ syntax: "*";
1120
+ inherits: false;
1121
+ }
1122
+ @property --tw-inset-shadow {
1123
+ syntax: "*";
1124
+ inherits: false;
1125
+ initial-value: 0 0 #0000;
1126
+ }
1127
+ @property --tw-inset-shadow-color {
1128
+ syntax: "*";
1129
+ inherits: false;
1130
+ }
1131
+ @property --tw-ring-color {
1132
+ syntax: "*";
1133
+ inherits: false;
1134
+ }
1135
+ @property --tw-ring-shadow {
1136
+ syntax: "*";
1137
+ inherits: false;
1138
+ initial-value: 0 0 #0000;
1139
+ }
1140
+ @property --tw-inset-ring-color {
1141
+ syntax: "*";
1142
+ inherits: false;
1143
+ }
1144
+ @property --tw-inset-ring-shadow {
1145
+ syntax: "*";
1146
+ inherits: false;
1147
+ initial-value: 0 0 #0000;
1148
+ }
1149
+ @property --tw-ring-inset {
1150
+ syntax: "*";
1151
+ inherits: false;
1152
+ }
1153
+ @property --tw-ring-offset-width {
1154
+ syntax: "<length>";
1155
+ inherits: false;
1156
+ initial-value: 0px;
1157
+ }
1158
+ @property --tw-ring-offset-color {
1159
+ syntax: "*";
1160
+ inherits: false;
1161
+ initial-value: #fff;
1162
+ }
1163
+ @property --tw-ring-offset-shadow {
1164
+ syntax: "*";
1165
+ inherits: false;
1166
+ initial-value: 0 0 #0000;
1167
+ }
@@ -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
  }
@@ -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:block ms:flex-col ms:w-full ms:border ms:border-neutral-300 ms:rounded ms:@container 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,33 +51,42 @@
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:w-full 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 ms:leading-normal", 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: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>
75
90
  <% end %>
76
91
  </div>
77
- </div>
78
92
  <% end %>
@@ -1,3 +1,3 @@
1
- <%= content_tag :div, class: "ms:prose ms:prose-zinc" do %>
1
+ <%= content_tag :div, class: "ms:prose ms:prose-neutral ms:max-w-none" do %>
2
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.11"
2
+ VERSION = "0.0.13"
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.11
4
+ version: 0.0.13
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