marksmith 0.0.13 → 0.0.15
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 +32 -20
- data/app/assets/config/marksmith_manifest.js +1 -0
- data/app/assets/javascripts/list_continuation_controller-full.esm.js +592 -0
- data/app/assets/javascripts/list_continuation_controller-no-stimulus.esm.js +102 -0
- data/app/assets/javascripts/marksmith_controller-full.esm.js +2939 -0
- data/app/assets/javascripts/marksmith_controller-no-stimulus.esm.js +2449 -0
- data/app/assets/stylesheets/marksmith.css +1 -1
- data/app/components/marksmith/markdown_field/edit_component.html.erb +8 -0
- data/app/components/marksmith/markdown_field/edit_component.rb +4 -0
- data/app/components/marksmith/markdown_field/show_component.html.erb +3 -0
- data/app/components/marksmith/markdown_field/show_component.rb +4 -0
- data/lib/marksmith/engine.rb +25 -0
- data/lib/marksmith/fields/markdown_field.rb +15 -0
- data/lib/marksmith/version.rb +1 -1
- metadata +11 -2
@@ -0,0 +1,592 @@
|
|
1
|
+
/*!
|
2
|
+
Marksmith 0.0.15
|
3
|
+
*/
|
4
|
+
var ListContinuationController = (function () {
|
5
|
+
'use strict';
|
6
|
+
|
7
|
+
/*
|
8
|
+
Stimulus 3.2.1
|
9
|
+
Copyright © 2023 Basecamp, LLC
|
10
|
+
*/
|
11
|
+
|
12
|
+
function camelize(value) {
|
13
|
+
return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase());
|
14
|
+
}
|
15
|
+
function namespaceCamelize(value) {
|
16
|
+
return camelize(value.replace(/--/g, "-").replace(/__/g, "_"));
|
17
|
+
}
|
18
|
+
function capitalize(value) {
|
19
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
20
|
+
}
|
21
|
+
function dasherize(value) {
|
22
|
+
return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`);
|
23
|
+
}
|
24
|
+
|
25
|
+
function isSomething(object) {
|
26
|
+
return object !== null && object !== undefined;
|
27
|
+
}
|
28
|
+
function hasProperty(object, property) {
|
29
|
+
return Object.prototype.hasOwnProperty.call(object, property);
|
30
|
+
}
|
31
|
+
|
32
|
+
function readInheritableStaticArrayValues(constructor, propertyName) {
|
33
|
+
const ancestors = getAncestorsForConstructor(constructor);
|
34
|
+
return Array.from(ancestors.reduce((values, constructor) => {
|
35
|
+
getOwnStaticArrayValues(constructor, propertyName).forEach((name) => values.add(name));
|
36
|
+
return values;
|
37
|
+
}, new Set()));
|
38
|
+
}
|
39
|
+
function readInheritableStaticObjectPairs(constructor, propertyName) {
|
40
|
+
const ancestors = getAncestorsForConstructor(constructor);
|
41
|
+
return ancestors.reduce((pairs, constructor) => {
|
42
|
+
pairs.push(...getOwnStaticObjectPairs(constructor, propertyName));
|
43
|
+
return pairs;
|
44
|
+
}, []);
|
45
|
+
}
|
46
|
+
function getAncestorsForConstructor(constructor) {
|
47
|
+
const ancestors = [];
|
48
|
+
while (constructor) {
|
49
|
+
ancestors.push(constructor);
|
50
|
+
constructor = Object.getPrototypeOf(constructor);
|
51
|
+
}
|
52
|
+
return ancestors.reverse();
|
53
|
+
}
|
54
|
+
function getOwnStaticArrayValues(constructor, propertyName) {
|
55
|
+
const definition = constructor[propertyName];
|
56
|
+
return Array.isArray(definition) ? definition : [];
|
57
|
+
}
|
58
|
+
function getOwnStaticObjectPairs(constructor, propertyName) {
|
59
|
+
const definition = constructor[propertyName];
|
60
|
+
return definition ? Object.keys(definition).map((key) => [key, definition[key]]) : [];
|
61
|
+
}
|
62
|
+
(() => {
|
63
|
+
function extendWithReflect(constructor) {
|
64
|
+
function extended() {
|
65
|
+
return Reflect.construct(constructor, arguments, new.target);
|
66
|
+
}
|
67
|
+
extended.prototype = Object.create(constructor.prototype, {
|
68
|
+
constructor: { value: extended },
|
69
|
+
});
|
70
|
+
Reflect.setPrototypeOf(extended, constructor);
|
71
|
+
return extended;
|
72
|
+
}
|
73
|
+
function testReflectExtension() {
|
74
|
+
const a = function () {
|
75
|
+
this.a.call(this);
|
76
|
+
};
|
77
|
+
const b = extendWithReflect(a);
|
78
|
+
b.prototype.a = function () { };
|
79
|
+
return new b();
|
80
|
+
}
|
81
|
+
try {
|
82
|
+
testReflectExtension();
|
83
|
+
return extendWithReflect;
|
84
|
+
}
|
85
|
+
catch (error) {
|
86
|
+
return (constructor) => class extended extends constructor {
|
87
|
+
};
|
88
|
+
}
|
89
|
+
})();
|
90
|
+
|
91
|
+
({
|
92
|
+
controllerAttribute: "data-controller",
|
93
|
+
actionAttribute: "data-action",
|
94
|
+
targetAttribute: "data-target",
|
95
|
+
targetAttributeForScope: (identifier) => `data-${identifier}-target`,
|
96
|
+
outletAttributeForScope: (identifier, outlet) => `data-${identifier}-${outlet}-outlet`,
|
97
|
+
keyMappings: Object.assign(Object.assign({ enter: "Enter", tab: "Tab", esc: "Escape", space: " ", up: "ArrowUp", down: "ArrowDown", left: "ArrowLeft", right: "ArrowRight", home: "Home", end: "End", page_up: "PageUp", page_down: "PageDown" }, objectFromEntries("abcdefghijklmnopqrstuvwxyz".split("").map((c) => [c, c]))), objectFromEntries("0123456789".split("").map((n) => [n, n]))),
|
98
|
+
});
|
99
|
+
function objectFromEntries(array) {
|
100
|
+
return array.reduce((memo, [k, v]) => (Object.assign(Object.assign({}, memo), { [k]: v })), {});
|
101
|
+
}
|
102
|
+
|
103
|
+
function ClassPropertiesBlessing(constructor) {
|
104
|
+
const classes = readInheritableStaticArrayValues(constructor, "classes");
|
105
|
+
return classes.reduce((properties, classDefinition) => {
|
106
|
+
return Object.assign(properties, propertiesForClassDefinition(classDefinition));
|
107
|
+
}, {});
|
108
|
+
}
|
109
|
+
function propertiesForClassDefinition(key) {
|
110
|
+
return {
|
111
|
+
[`${key}Class`]: {
|
112
|
+
get() {
|
113
|
+
const { classes } = this;
|
114
|
+
if (classes.has(key)) {
|
115
|
+
return classes.get(key);
|
116
|
+
}
|
117
|
+
else {
|
118
|
+
const attribute = classes.getAttributeName(key);
|
119
|
+
throw new Error(`Missing attribute "${attribute}"`);
|
120
|
+
}
|
121
|
+
},
|
122
|
+
},
|
123
|
+
[`${key}Classes`]: {
|
124
|
+
get() {
|
125
|
+
return this.classes.getAll(key);
|
126
|
+
},
|
127
|
+
},
|
128
|
+
[`has${capitalize(key)}Class`]: {
|
129
|
+
get() {
|
130
|
+
return this.classes.has(key);
|
131
|
+
},
|
132
|
+
},
|
133
|
+
};
|
134
|
+
}
|
135
|
+
|
136
|
+
function OutletPropertiesBlessing(constructor) {
|
137
|
+
const outlets = readInheritableStaticArrayValues(constructor, "outlets");
|
138
|
+
return outlets.reduce((properties, outletDefinition) => {
|
139
|
+
return Object.assign(properties, propertiesForOutletDefinition(outletDefinition));
|
140
|
+
}, {});
|
141
|
+
}
|
142
|
+
function getOutletController(controller, element, identifier) {
|
143
|
+
return controller.application.getControllerForElementAndIdentifier(element, identifier);
|
144
|
+
}
|
145
|
+
function getControllerAndEnsureConnectedScope(controller, element, outletName) {
|
146
|
+
let outletController = getOutletController(controller, element, outletName);
|
147
|
+
if (outletController)
|
148
|
+
return outletController;
|
149
|
+
controller.application.router.proposeToConnectScopeForElementAndIdentifier(element, outletName);
|
150
|
+
outletController = getOutletController(controller, element, outletName);
|
151
|
+
if (outletController)
|
152
|
+
return outletController;
|
153
|
+
}
|
154
|
+
function propertiesForOutletDefinition(name) {
|
155
|
+
const camelizedName = namespaceCamelize(name);
|
156
|
+
return {
|
157
|
+
[`${camelizedName}Outlet`]: {
|
158
|
+
get() {
|
159
|
+
const outletElement = this.outlets.find(name);
|
160
|
+
const selector = this.outlets.getSelectorForOutletName(name);
|
161
|
+
if (outletElement) {
|
162
|
+
const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
|
163
|
+
if (outletController)
|
164
|
+
return outletController;
|
165
|
+
throw new Error(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`);
|
166
|
+
}
|
167
|
+
throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
|
168
|
+
},
|
169
|
+
},
|
170
|
+
[`${camelizedName}Outlets`]: {
|
171
|
+
get() {
|
172
|
+
const outlets = this.outlets.findAll(name);
|
173
|
+
if (outlets.length > 0) {
|
174
|
+
return outlets
|
175
|
+
.map((outletElement) => {
|
176
|
+
const outletController = getControllerAndEnsureConnectedScope(this, outletElement, name);
|
177
|
+
if (outletController)
|
178
|
+
return outletController;
|
179
|
+
console.warn(`The provided outlet element is missing an outlet controller "${name}" instance for host controller "${this.identifier}"`, outletElement);
|
180
|
+
})
|
181
|
+
.filter((controller) => controller);
|
182
|
+
}
|
183
|
+
return [];
|
184
|
+
},
|
185
|
+
},
|
186
|
+
[`${camelizedName}OutletElement`]: {
|
187
|
+
get() {
|
188
|
+
const outletElement = this.outlets.find(name);
|
189
|
+
const selector = this.outlets.getSelectorForOutletName(name);
|
190
|
+
if (outletElement) {
|
191
|
+
return outletElement;
|
192
|
+
}
|
193
|
+
else {
|
194
|
+
throw new Error(`Missing outlet element "${name}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${selector}".`);
|
195
|
+
}
|
196
|
+
},
|
197
|
+
},
|
198
|
+
[`${camelizedName}OutletElements`]: {
|
199
|
+
get() {
|
200
|
+
return this.outlets.findAll(name);
|
201
|
+
},
|
202
|
+
},
|
203
|
+
[`has${capitalize(camelizedName)}Outlet`]: {
|
204
|
+
get() {
|
205
|
+
return this.outlets.has(name);
|
206
|
+
},
|
207
|
+
},
|
208
|
+
};
|
209
|
+
}
|
210
|
+
|
211
|
+
function TargetPropertiesBlessing(constructor) {
|
212
|
+
const targets = readInheritableStaticArrayValues(constructor, "targets");
|
213
|
+
return targets.reduce((properties, targetDefinition) => {
|
214
|
+
return Object.assign(properties, propertiesForTargetDefinition(targetDefinition));
|
215
|
+
}, {});
|
216
|
+
}
|
217
|
+
function propertiesForTargetDefinition(name) {
|
218
|
+
return {
|
219
|
+
[`${name}Target`]: {
|
220
|
+
get() {
|
221
|
+
const target = this.targets.find(name);
|
222
|
+
if (target) {
|
223
|
+
return target;
|
224
|
+
}
|
225
|
+
else {
|
226
|
+
throw new Error(`Missing target element "${name}" for "${this.identifier}" controller`);
|
227
|
+
}
|
228
|
+
},
|
229
|
+
},
|
230
|
+
[`${name}Targets`]: {
|
231
|
+
get() {
|
232
|
+
return this.targets.findAll(name);
|
233
|
+
},
|
234
|
+
},
|
235
|
+
[`has${capitalize(name)}Target`]: {
|
236
|
+
get() {
|
237
|
+
return this.targets.has(name);
|
238
|
+
},
|
239
|
+
},
|
240
|
+
};
|
241
|
+
}
|
242
|
+
|
243
|
+
function ValuePropertiesBlessing(constructor) {
|
244
|
+
const valueDefinitionPairs = readInheritableStaticObjectPairs(constructor, "values");
|
245
|
+
const propertyDescriptorMap = {
|
246
|
+
valueDescriptorMap: {
|
247
|
+
get() {
|
248
|
+
return valueDefinitionPairs.reduce((result, valueDefinitionPair) => {
|
249
|
+
const valueDescriptor = parseValueDefinitionPair(valueDefinitionPair, this.identifier);
|
250
|
+
const attributeName = this.data.getAttributeNameForKey(valueDescriptor.key);
|
251
|
+
return Object.assign(result, { [attributeName]: valueDescriptor });
|
252
|
+
}, {});
|
253
|
+
},
|
254
|
+
},
|
255
|
+
};
|
256
|
+
return valueDefinitionPairs.reduce((properties, valueDefinitionPair) => {
|
257
|
+
return Object.assign(properties, propertiesForValueDefinitionPair(valueDefinitionPair));
|
258
|
+
}, propertyDescriptorMap);
|
259
|
+
}
|
260
|
+
function propertiesForValueDefinitionPair(valueDefinitionPair, controller) {
|
261
|
+
const definition = parseValueDefinitionPair(valueDefinitionPair, controller);
|
262
|
+
const { key, name, reader: read, writer: write } = definition;
|
263
|
+
return {
|
264
|
+
[name]: {
|
265
|
+
get() {
|
266
|
+
const value = this.data.get(key);
|
267
|
+
if (value !== null) {
|
268
|
+
return read(value);
|
269
|
+
}
|
270
|
+
else {
|
271
|
+
return definition.defaultValue;
|
272
|
+
}
|
273
|
+
},
|
274
|
+
set(value) {
|
275
|
+
if (value === undefined) {
|
276
|
+
this.data.delete(key);
|
277
|
+
}
|
278
|
+
else {
|
279
|
+
this.data.set(key, write(value));
|
280
|
+
}
|
281
|
+
},
|
282
|
+
},
|
283
|
+
[`has${capitalize(name)}`]: {
|
284
|
+
get() {
|
285
|
+
return this.data.has(key) || definition.hasCustomDefaultValue;
|
286
|
+
},
|
287
|
+
},
|
288
|
+
};
|
289
|
+
}
|
290
|
+
function parseValueDefinitionPair([token, typeDefinition], controller) {
|
291
|
+
return valueDescriptorForTokenAndTypeDefinition({
|
292
|
+
controller,
|
293
|
+
token,
|
294
|
+
typeDefinition,
|
295
|
+
});
|
296
|
+
}
|
297
|
+
function parseValueTypeConstant(constant) {
|
298
|
+
switch (constant) {
|
299
|
+
case Array:
|
300
|
+
return "array";
|
301
|
+
case Boolean:
|
302
|
+
return "boolean";
|
303
|
+
case Number:
|
304
|
+
return "number";
|
305
|
+
case Object:
|
306
|
+
return "object";
|
307
|
+
case String:
|
308
|
+
return "string";
|
309
|
+
}
|
310
|
+
}
|
311
|
+
function parseValueTypeDefault(defaultValue) {
|
312
|
+
switch (typeof defaultValue) {
|
313
|
+
case "boolean":
|
314
|
+
return "boolean";
|
315
|
+
case "number":
|
316
|
+
return "number";
|
317
|
+
case "string":
|
318
|
+
return "string";
|
319
|
+
}
|
320
|
+
if (Array.isArray(defaultValue))
|
321
|
+
return "array";
|
322
|
+
if (Object.prototype.toString.call(defaultValue) === "[object Object]")
|
323
|
+
return "object";
|
324
|
+
}
|
325
|
+
function parseValueTypeObject(payload) {
|
326
|
+
const { controller, token, typeObject } = payload;
|
327
|
+
const hasType = isSomething(typeObject.type);
|
328
|
+
const hasDefault = isSomething(typeObject.default);
|
329
|
+
const fullObject = hasType && hasDefault;
|
330
|
+
const onlyType = hasType && !hasDefault;
|
331
|
+
const onlyDefault = !hasType && hasDefault;
|
332
|
+
const typeFromObject = parseValueTypeConstant(typeObject.type);
|
333
|
+
const typeFromDefaultValue = parseValueTypeDefault(payload.typeObject.default);
|
334
|
+
if (onlyType)
|
335
|
+
return typeFromObject;
|
336
|
+
if (onlyDefault)
|
337
|
+
return typeFromDefaultValue;
|
338
|
+
if (typeFromObject !== typeFromDefaultValue) {
|
339
|
+
const propertyPath = controller ? `${controller}.${token}` : token;
|
340
|
+
throw new Error(`The specified default value for the Stimulus Value "${propertyPath}" must match the defined type "${typeFromObject}". The provided default value of "${typeObject.default}" is of type "${typeFromDefaultValue}".`);
|
341
|
+
}
|
342
|
+
if (fullObject)
|
343
|
+
return typeFromObject;
|
344
|
+
}
|
345
|
+
function parseValueTypeDefinition(payload) {
|
346
|
+
const { controller, token, typeDefinition } = payload;
|
347
|
+
const typeObject = { controller, token, typeObject: typeDefinition };
|
348
|
+
const typeFromObject = parseValueTypeObject(typeObject);
|
349
|
+
const typeFromDefaultValue = parseValueTypeDefault(typeDefinition);
|
350
|
+
const typeFromConstant = parseValueTypeConstant(typeDefinition);
|
351
|
+
const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
|
352
|
+
if (type)
|
353
|
+
return type;
|
354
|
+
const propertyPath = controller ? `${controller}.${typeDefinition}` : token;
|
355
|
+
throw new Error(`Unknown value type "${propertyPath}" for "${token}" value`);
|
356
|
+
}
|
357
|
+
function defaultValueForDefinition(typeDefinition) {
|
358
|
+
const constant = parseValueTypeConstant(typeDefinition);
|
359
|
+
if (constant)
|
360
|
+
return defaultValuesByType[constant];
|
361
|
+
const hasDefault = hasProperty(typeDefinition, "default");
|
362
|
+
const hasType = hasProperty(typeDefinition, "type");
|
363
|
+
const typeObject = typeDefinition;
|
364
|
+
if (hasDefault)
|
365
|
+
return typeObject.default;
|
366
|
+
if (hasType) {
|
367
|
+
const { type } = typeObject;
|
368
|
+
const constantFromType = parseValueTypeConstant(type);
|
369
|
+
if (constantFromType)
|
370
|
+
return defaultValuesByType[constantFromType];
|
371
|
+
}
|
372
|
+
return typeDefinition;
|
373
|
+
}
|
374
|
+
function valueDescriptorForTokenAndTypeDefinition(payload) {
|
375
|
+
const { token, typeDefinition } = payload;
|
376
|
+
const key = `${dasherize(token)}-value`;
|
377
|
+
const type = parseValueTypeDefinition(payload);
|
378
|
+
return {
|
379
|
+
type,
|
380
|
+
key,
|
381
|
+
name: camelize(key),
|
382
|
+
get defaultValue() {
|
383
|
+
return defaultValueForDefinition(typeDefinition);
|
384
|
+
},
|
385
|
+
get hasCustomDefaultValue() {
|
386
|
+
return parseValueTypeDefault(typeDefinition) !== undefined;
|
387
|
+
},
|
388
|
+
reader: readers[type],
|
389
|
+
writer: writers[type] || writers.default,
|
390
|
+
};
|
391
|
+
}
|
392
|
+
const defaultValuesByType = {
|
393
|
+
get array() {
|
394
|
+
return [];
|
395
|
+
},
|
396
|
+
boolean: false,
|
397
|
+
number: 0,
|
398
|
+
get object() {
|
399
|
+
return {};
|
400
|
+
},
|
401
|
+
string: "",
|
402
|
+
};
|
403
|
+
const readers = {
|
404
|
+
array(value) {
|
405
|
+
const array = JSON.parse(value);
|
406
|
+
if (!Array.isArray(array)) {
|
407
|
+
throw new TypeError(`expected value of type "array" but instead got value "${value}" of type "${parseValueTypeDefault(array)}"`);
|
408
|
+
}
|
409
|
+
return array;
|
410
|
+
},
|
411
|
+
boolean(value) {
|
412
|
+
return !(value == "0" || String(value).toLowerCase() == "false");
|
413
|
+
},
|
414
|
+
number(value) {
|
415
|
+
return Number(value.replace(/_/g, ""));
|
416
|
+
},
|
417
|
+
object(value) {
|
418
|
+
const object = JSON.parse(value);
|
419
|
+
if (object === null || typeof object != "object" || Array.isArray(object)) {
|
420
|
+
throw new TypeError(`expected value of type "object" but instead got value "${value}" of type "${parseValueTypeDefault(object)}"`);
|
421
|
+
}
|
422
|
+
return object;
|
423
|
+
},
|
424
|
+
string(value) {
|
425
|
+
return value;
|
426
|
+
},
|
427
|
+
};
|
428
|
+
const writers = {
|
429
|
+
default: writeString,
|
430
|
+
array: writeJSON,
|
431
|
+
object: writeJSON,
|
432
|
+
};
|
433
|
+
function writeJSON(value) {
|
434
|
+
return JSON.stringify(value);
|
435
|
+
}
|
436
|
+
function writeString(value) {
|
437
|
+
return `${value}`;
|
438
|
+
}
|
439
|
+
|
440
|
+
class Controller {
|
441
|
+
constructor(context) {
|
442
|
+
this.context = context;
|
443
|
+
}
|
444
|
+
static get shouldLoad() {
|
445
|
+
return true;
|
446
|
+
}
|
447
|
+
static afterLoad(_identifier, _application) {
|
448
|
+
return;
|
449
|
+
}
|
450
|
+
get application() {
|
451
|
+
return this.context.application;
|
452
|
+
}
|
453
|
+
get scope() {
|
454
|
+
return this.context.scope;
|
455
|
+
}
|
456
|
+
get element() {
|
457
|
+
return this.scope.element;
|
458
|
+
}
|
459
|
+
get identifier() {
|
460
|
+
return this.scope.identifier;
|
461
|
+
}
|
462
|
+
get targets() {
|
463
|
+
return this.scope.targets;
|
464
|
+
}
|
465
|
+
get outlets() {
|
466
|
+
return this.scope.outlets;
|
467
|
+
}
|
468
|
+
get classes() {
|
469
|
+
return this.scope.classes;
|
470
|
+
}
|
471
|
+
get data() {
|
472
|
+
return this.scope.data;
|
473
|
+
}
|
474
|
+
initialize() {
|
475
|
+
}
|
476
|
+
connect() {
|
477
|
+
}
|
478
|
+
disconnect() {
|
479
|
+
}
|
480
|
+
dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true, } = {}) {
|
481
|
+
const type = prefix ? `${prefix}:${eventName}` : eventName;
|
482
|
+
const event = new CustomEvent(type, { detail, bubbles, cancelable });
|
483
|
+
target.dispatchEvent(event);
|
484
|
+
return event;
|
485
|
+
}
|
486
|
+
}
|
487
|
+
Controller.blessings = [
|
488
|
+
ClassPropertiesBlessing,
|
489
|
+
TargetPropertiesBlessing,
|
490
|
+
ValuePropertiesBlessing,
|
491
|
+
OutletPropertiesBlessing,
|
492
|
+
];
|
493
|
+
Controller.targets = [];
|
494
|
+
Controller.outlets = [];
|
495
|
+
Controller.values = {};
|
496
|
+
|
497
|
+
class list_continuation_controller extends Controller {
|
498
|
+
connect() {
|
499
|
+
this.isInsertLineBreak = false;
|
500
|
+
this.isProcessing = false; // Guard flag to prevent recursion
|
501
|
+
|
502
|
+
this.SPACE_PATTERN = /^(\s*)?/;
|
503
|
+
this.LIST_PATTERN = /^(\s*)([*-]|(\d+)\.)\s(\[[\sx]\]\s)?/;
|
504
|
+
}
|
505
|
+
|
506
|
+
handleBeforeInput(event) {
|
507
|
+
if (this.isProcessing) return
|
508
|
+
this.isInsertLineBreak = event.inputType === 'insertLineBreak';
|
509
|
+
}
|
510
|
+
|
511
|
+
handleInput(event) {
|
512
|
+
if (this.isProcessing) return
|
513
|
+
if (this.isInsertLineBreak || event.inputType === 'insertLineBreak') {
|
514
|
+
this.handleListContinuation(event.target);
|
515
|
+
this.isInsertLineBreak = false;
|
516
|
+
}
|
517
|
+
}
|
518
|
+
|
519
|
+
handleListContinuation(textarea) {
|
520
|
+
if (this.isProcessing) return
|
521
|
+
|
522
|
+
const result = this.analyzeCurrentLine(
|
523
|
+
textarea.value,
|
524
|
+
[textarea.selectionStart, textarea.selectionEnd],
|
525
|
+
);
|
526
|
+
|
527
|
+
if (result !== undefined) {
|
528
|
+
this.isProcessing = true;
|
529
|
+
try {
|
530
|
+
this.applyTextChange(textarea, result);
|
531
|
+
} finally {
|
532
|
+
// Ensure we always reset the processing flag
|
533
|
+
setTimeout(() => {
|
534
|
+
this.isProcessing = false;
|
535
|
+
}, 0);
|
536
|
+
}
|
537
|
+
}
|
538
|
+
}
|
539
|
+
|
540
|
+
analyzeCurrentLine(text, [cursorPosition]) {
|
541
|
+
if (!cursorPosition || !text) return
|
542
|
+
|
543
|
+
// Get all lines up to cursor
|
544
|
+
const lines = text.substring(0, cursorPosition).split('\n');
|
545
|
+
const previousLine = lines[lines.length - 2];
|
546
|
+
|
547
|
+
// If no previous line or doesn't match list pattern, do nothing
|
548
|
+
const match = previousLine?.match(this.LIST_PATTERN);
|
549
|
+
if (!match) return
|
550
|
+
|
551
|
+
const [fullMatch, indentation, listMarker, number, checkbox] = match;
|
552
|
+
|
553
|
+
// Check if previous line was empty (just list marker)
|
554
|
+
const previousContent = previousLine.replace(fullMatch, '').trim();
|
555
|
+
if (previousContent.length === 0) {
|
556
|
+
// Terminate the list by removing the marker
|
557
|
+
const start = cursorPosition - `\n${fullMatch}`.length;
|
558
|
+
|
559
|
+
return {
|
560
|
+
text: text.substring(0, start) + text.substring(cursorPosition),
|
561
|
+
selection: [start, start],
|
562
|
+
operation: 'delete',
|
563
|
+
}
|
564
|
+
}
|
565
|
+
|
566
|
+
// For numbered lists, increment the number
|
567
|
+
const newMarker = number ? `${parseInt(number, 10) + 1}.` : listMarker;
|
568
|
+
|
569
|
+
// Maintain checkbox if it was present
|
570
|
+
const prefix = `${indentation}${newMarker} ${checkbox ? '[ ] ' : ''}`;
|
571
|
+
|
572
|
+
// Continue the list with the same indentation and style
|
573
|
+
return {
|
574
|
+
text: text.substring(0, cursorPosition) + prefix + text.substring(cursorPosition),
|
575
|
+
selection: [cursorPosition + prefix.length, cursorPosition + prefix.length],
|
576
|
+
operation: 'insert',
|
577
|
+
}
|
578
|
+
}
|
579
|
+
|
580
|
+
applyTextChange(textarea, { text, selection }) {
|
581
|
+
// Set new value directly
|
582
|
+
textarea.value = text;
|
583
|
+
// Set the cursor position
|
584
|
+
const [start, end] = selection;
|
585
|
+
textarea.selectionStart = start;
|
586
|
+
textarea.selectionEnd = end;
|
587
|
+
}
|
588
|
+
}
|
589
|
+
|
590
|
+
return list_continuation_controller;
|
591
|
+
|
592
|
+
})();
|
@@ -0,0 +1,102 @@
|
|
1
|
+
/*!
|
2
|
+
Marksmith 0.0.15
|
3
|
+
*/
|
4
|
+
var ListContinuationController = (function (stimulus) {
|
5
|
+
'use strict';
|
6
|
+
|
7
|
+
class list_continuation_controller extends stimulus.Controller {
|
8
|
+
connect() {
|
9
|
+
this.isInsertLineBreak = false;
|
10
|
+
this.isProcessing = false; // Guard flag to prevent recursion
|
11
|
+
|
12
|
+
this.SPACE_PATTERN = /^(\s*)?/;
|
13
|
+
this.LIST_PATTERN = /^(\s*)([*-]|(\d+)\.)\s(\[[\sx]\]\s)?/;
|
14
|
+
}
|
15
|
+
|
16
|
+
handleBeforeInput(event) {
|
17
|
+
if (this.isProcessing) return
|
18
|
+
this.isInsertLineBreak = event.inputType === 'insertLineBreak';
|
19
|
+
}
|
20
|
+
|
21
|
+
handleInput(event) {
|
22
|
+
if (this.isProcessing) return
|
23
|
+
if (this.isInsertLineBreak || event.inputType === 'insertLineBreak') {
|
24
|
+
this.handleListContinuation(event.target);
|
25
|
+
this.isInsertLineBreak = false;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
handleListContinuation(textarea) {
|
30
|
+
if (this.isProcessing) return
|
31
|
+
|
32
|
+
const result = this.analyzeCurrentLine(
|
33
|
+
textarea.value,
|
34
|
+
[textarea.selectionStart, textarea.selectionEnd],
|
35
|
+
);
|
36
|
+
|
37
|
+
if (result !== undefined) {
|
38
|
+
this.isProcessing = true;
|
39
|
+
try {
|
40
|
+
this.applyTextChange(textarea, result);
|
41
|
+
} finally {
|
42
|
+
// Ensure we always reset the processing flag
|
43
|
+
setTimeout(() => {
|
44
|
+
this.isProcessing = false;
|
45
|
+
}, 0);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
analyzeCurrentLine(text, [cursorPosition]) {
|
51
|
+
if (!cursorPosition || !text) return
|
52
|
+
|
53
|
+
// Get all lines up to cursor
|
54
|
+
const lines = text.substring(0, cursorPosition).split('\n');
|
55
|
+
const previousLine = lines[lines.length - 2];
|
56
|
+
|
57
|
+
// If no previous line or doesn't match list pattern, do nothing
|
58
|
+
const match = previousLine?.match(this.LIST_PATTERN);
|
59
|
+
if (!match) return
|
60
|
+
|
61
|
+
const [fullMatch, indentation, listMarker, number, checkbox] = match;
|
62
|
+
|
63
|
+
// Check if previous line was empty (just list marker)
|
64
|
+
const previousContent = previousLine.replace(fullMatch, '').trim();
|
65
|
+
if (previousContent.length === 0) {
|
66
|
+
// Terminate the list by removing the marker
|
67
|
+
const start = cursorPosition - `\n${fullMatch}`.length;
|
68
|
+
|
69
|
+
return {
|
70
|
+
text: text.substring(0, start) + text.substring(cursorPosition),
|
71
|
+
selection: [start, start],
|
72
|
+
operation: 'delete',
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
// For numbered lists, increment the number
|
77
|
+
const newMarker = number ? `${parseInt(number, 10) + 1}.` : listMarker;
|
78
|
+
|
79
|
+
// Maintain checkbox if it was present
|
80
|
+
const prefix = `${indentation}${newMarker} ${checkbox ? '[ ] ' : ''}`;
|
81
|
+
|
82
|
+
// Continue the list with the same indentation and style
|
83
|
+
return {
|
84
|
+
text: text.substring(0, cursorPosition) + prefix + text.substring(cursorPosition),
|
85
|
+
selection: [cursorPosition + prefix.length, cursorPosition + prefix.length],
|
86
|
+
operation: 'insert',
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
applyTextChange(textarea, { text, selection }) {
|
91
|
+
// Set new value directly
|
92
|
+
textarea.value = text;
|
93
|
+
// Set the cursor position
|
94
|
+
const [start, end] = selection;
|
95
|
+
textarea.selectionStart = start;
|
96
|
+
textarea.selectionEnd = end;
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
return list_continuation_controller;
|
101
|
+
|
102
|
+
})(FakeStimulus);
|