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 +4 -4
- data/README.md +18 -4
- data/app/assets/images/marksmith/svgs/markdown.svg +1 -0
- data/app/assets/images/marksmith/svgs/paperclip.svg +3 -0
- data/app/assets/stylesheets/marksmith.css +242 -40
- data/app/frontend/entrypoints/application.css +51 -0
- data/app/frontend/entrypoints/javascript/controllers/export.js +6 -0
- data/app/frontend/entrypoints/javascript/controllers/list_continuation_controller.js +94 -0
- data/app/frontend/entrypoints/javascript/controllers/marksmith_controller.js +19 -4
- data/app/views/marksmith/shared/_editor.html.erb +39 -25
- data/app/views/marksmith/shared/_rendered_body.html.erb +1 -1
- data/config/locales/marksmith.en.yml +2 -0
- data/lib/marksmith/helper.rb +1 -1
- data/lib/marksmith/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0128324daeb57601c2ba3ab60de880179bb6df7dfcf4e5ead34aee95d35aeb0b'
|
4
|
+
data.tar.gz: 0cee7e267e53b84330fbad9b9246a5bafb67234f84a232007f199227ad72358d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
53
|
+
import { MarksmithController } from '@avo-hq/marksmith'
|
54
54
|
|
55
|
-
application.register('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
|
64
|
+
import { MarksmithController } from '@avo-hq/marksmith/core'
|
65
65
|
|
66
|
-
application.register('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
|
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-
|
848
|
-
border-color: var(--ms-color-
|
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-
|
851
|
-
background-color: var(--ms-color-
|
882
|
+
.ms\:bg-neutral-200 {
|
883
|
+
background-color: var(--ms-color-neutral-200);
|
852
884
|
}
|
853
|
-
.ms\:bg-
|
854
|
-
background-color:
|
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\:
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
--tw-
|
886
|
-
|
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.
|
889
|
-
--tw-prose-pre-code: oklch(0.
|
890
|
-
--tw-prose-pre-bg: oklch(0.
|
891
|
-
--tw-prose-th-borders: oklch(0.
|
892
|
-
--tw-prose-td-borders: oklch(0.
|
893
|
-
--tw-prose-invert-body: oklch(0.
|
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.
|
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.
|
899
|
-
--tw-prose-invert-bullets: oklch(0.
|
900
|
-
--tw-prose-invert-hr: oklch(0.
|
901
|
-
--tw-prose-invert-quotes: oklch(0.
|
902
|
-
--tw-prose-invert-quote-borders: oklch(0.
|
903
|
-
--tw-prose-invert-captions: oklch(0.
|
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.
|
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.
|
910
|
-
--tw-prose-invert-td-borders: oklch(0.
|
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-
|
987
|
+
.ms\:focus-within\:border-neutral-400 {
|
913
988
|
&:focus-within {
|
914
|
-
border-color: var(--ms-color-
|
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-
|
999
|
+
.ms\:hover\:bg-neutral-200 {
|
918
1000
|
&:hover {
|
919
1001
|
@media (hover: hover) {
|
920
|
-
background-color: var(--ms-color-
|
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,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.
|
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.
|
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.
|
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:
|
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-
|
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-
|
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-
|
51
|
-
<%=
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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-
|
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
|
data/lib/marksmith/helper.rb
CHANGED
@@ -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-
|
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)
|
data/lib/marksmith/version.rb
CHANGED
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.
|
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-
|
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
|