marksmith 0.0.11 → 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
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