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 +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 +231 -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/markdown_previews/create.turbo_stream.erb +1 -1
- data/app/views/marksmith/shared/_editor.html.erb +39 -24
- data/app/views/marksmith/shared/_rendered_body.html.erb +2 -2
- 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: 6223365f49536a9ee694cd03965e95e7ad4678f1baf8c2d5b8326b4e255f3c14
|
4
|
+
data.tar.gz: 300bf849b18a10fd9acedddfcfdff606e207d923d0aa9faa7f29a5e0a5703c46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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';
|
@@ -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-
|
848
|
-
border-color: var(--ms-color-
|
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-
|
851
|
-
background-color: var(--ms-color-
|
879
|
+
.ms\:bg-neutral-200 {
|
880
|
+
background-color: var(--ms-color-neutral-200);
|
852
881
|
}
|
853
|
-
.ms\:bg-
|
854
|
-
background-color:
|
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\:
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
--tw-
|
886
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
910
|
-
--tw-prose-invert-td-borders: oklch(0.
|
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-
|
980
|
+
.ms\:focus-within\:border-neutral-400 {
|
913
981
|
&:focus-within {
|
914
|
-
border-color: var(--ms-color-
|
982
|
+
border-color: var(--ms-color-neutral-400);
|
915
983
|
}
|
916
984
|
}
|
917
|
-
.ms\:hover\:bg-
|
985
|
+
.ms\:hover\:bg-neutral-100 {
|
918
986
|
&:hover {
|
919
987
|
@media (hover: hover) {
|
920
|
-
background-color: var(--ms-color-
|
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,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:flex ms:flex-col ms:w-full ms:border ms:border-
|
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-
|
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,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-
|
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: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-
|
2
|
-
<%= sanitize(
|
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
|
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.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-
|
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
|