diff --git a/.gitignore b/.gitignore index c7996e73..9c6d7093 100755 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ node_modules yarn.lock test/components/*.html versions -releases docs dist example diff --git a/.prettierignore b/.prettierignore index 99066eb6..88cfc662 100755 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,7 @@ dist/ src/language/liquid.configuration.json test/format.html +schema/ *.ts *.js *.cjs diff --git a/.vscode/launch.json b/.vscode/launch.json index e03949dc..cf7f6079 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,20 +11,17 @@ "name": "Attach", "port": 9229 }, - { "name": "Extension", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", - "args": ["--disable-extensions", "--extensionDevelopmentPath=${workspaceFolder}", "${workspaceFolder}/test"] - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": ["--disable-extensions","--extensionDevelopmentPath=${workspaceFolder}","--extensionTestsPath=${workspaceFolder}/test"] + "args": ["--disable-extensions", "--extensionDevelopmentPath=${workspaceFolder}", "${workspaceFolder}/test"], + "sourceMaps": false, + "resolveSourceMapLocations": [ + "${workspaceFolder}/**", + "!**/node_modules/**" + ] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index afb62965..20999e41 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,10 @@ { "cSpell.words": [ "Allman", + "esbenp", "Mansedan", "packagejson", + "TLDR", "VSIX" ], "files.associations": { @@ -13,7 +15,15 @@ "*.css.liquid": "liquid-css", "*.scss.liquid": "liquid-scss" }, - "liquid.format.json": { - "braceAllman": true + "json.schemas": [ + { + "fileMatch": [ + "package.json" + ], + "url": "https://unpkg.com/tsup/schema.json" + } + ], + "liquid.format.rules": { + "json": {} } -} +} \ No newline at end of file diff --git a/extension/activate.ts b/extension/activate.ts new file mode 100644 index 00000000..c0ddcd57 --- /dev/null +++ b/extension/activate.ts @@ -0,0 +1,169 @@ +import { ConfigMethod } from './types'; +import { Events } from './events'; +import { languages, workspace, commands, ConfigurationTarget, ExtensionContext, window } from 'vscode'; +import esthetic from 'esthetic'; + +class VSCodeLiquid extends Events { + + /** + * Activate Extension + * + * Initialize the vscode-liquid extension + */ + async activate (subscriptions: { dispose(): void; }[]) { + + try { + + await this.getSettings(); + await this.setFeatures(); + + this.getWatchers(); + this.getFormatter(); + this.workspace(subscriptions); + this.register(subscriptions); + this.commands(subscriptions); + this.listeners(subscriptions); + this.windows(subscriptions); + + if (this.config.method === ConfigMethod.Workspace) { + + esthetic.settings({ logColors: false }).rules(this.formatting.rules); + + this.nl(); + this.info('workspace settings used for configuration'); + + } else if (this.config.method === ConfigMethod.Liquidrc) { + + esthetic.settings({ logColors: false }).rules(this.formatting.rules); + + this.nl(); + this.info(`.liquidrc file used for settings: ${this.uri.liquidrc.path}`); + + } + + // console.log(subscriptions); + + await this.onDidChangeActiveTextEditor(window.activeTextEditor); + + if (!this.hasActivated) this.hasActivated = true; + + } catch (e) { + + this.status.error('Extension could not be initialized'); + + this.catch('Extension could not be initialized', e); + + } + + } + + /** + * Restart Extension + * + * Restart the extension, re-activation applied by calling back to `activate`. + */ + private restart = (subscriptions: { dispose(): void; }[]) => async () => { + + await this.status.loading('Restarting extension...'); + + this.info('RESTARTING EXTENSION'); + + this.isReady = false; + this.canFormat = false; + this.config.method = ConfigMethod.Workspace; + this.config.target = ConfigurationTarget.Workspace; + this.uri.liquidrc = null; + this.formatting.reset(); + this.getWatchers(false); + + for (const subscription of subscriptions) subscription.dispose(); + + await this.activate(subscriptions); + + this.info('EXTENSION RESTARTED'); + + }; + + private listeners (subscriptions: { dispose(): void; }[]) { + + this.formatting.listen.event(this.onFormattingEvent, this, subscriptions); + + } + + /** + * Window Events + * + * Subscribe all window related events of the client. + */ + private windows (subscriptions: { dispose(): void; }[]) { + + window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, subscriptions); + + } + + /** + * Workspace Events + * + * Subscribe all workspace related events of the client. + */ + private workspace (subscriptions: { dispose(): void; }[]) { + + workspace.onDidRenameFiles(this.onDidRenameFiles, this, subscriptions); + workspace.onDidDeleteFiles(this.onDidDeleteFiles, this, subscriptions); + workspace.onDidCreateFiles(this.onDidCreateFiles, this, subscriptions); + workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, subscriptions); + workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, subscriptions); + workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, subscriptions); + workspace.onDidSaveTextDocument(this.onDidSaveTextDocument, this, subscriptions); + + } + + /** + * Language Events + * + * Subscribe all language related events of the client. + */ + private register (subscriptions: { dispose(): void; }[]) { + + subscriptions.push( + languages.registerDocumentLinkProvider(this.selector, this.links), + languages.registerHoverProvider(this.selector, this.hovers), + languages.registerCompletionItemProvider(this.selector, this.completion, ...this.completion.triggers), + languages.registerDocumentFormattingEditProvider(this.selector, this.formatting) + ); + + } + + /** + * Command Events + * + * Subscribe all command related events of the client. + */ + private commands (subscriptions: { dispose(): void; }[]) { + + subscriptions.push( + commands.registerCommand('liquid.generateLiquidrc', this.generateLiquidrc, this), + commands.registerCommand('liquid.openOutput', this.toggleOutput, this), + commands.registerCommand('liquid.formatDocument', this.formatDocument, this), + commands.registerCommand('liquid.enableFormatting', this.enableFormatting, this), + commands.registerCommand('liquid.disableFormatting', this.disableFormatting, this), + commands.registerCommand('liquid.releaseNotes', this.releaseNotes, this), + commands.registerCommand('liquid.restartExtension', this.restart(subscriptions)) + ); + + } + +} + +/** + * vscode-liquid + * + * Language features for working with the Liquid Template Language. + */ +export async function activate ({ subscriptions, extension }: ExtensionContext) { + + const liquid = new VSCodeLiquid(extension); + + await liquid.activate(subscriptions); + +}; diff --git a/extension/data/liquid.ts b/extension/data/liquid.ts new file mode 100644 index 00000000..78fd7941 --- /dev/null +++ b/extension/data/liquid.ts @@ -0,0 +1,432 @@ +import slash, { entries, keys } from 'utils'; +import { basename, join, dirname } from 'node:path'; +import { Filter, Tags, IObject, Type, Types, liquid, IProperty, $, p } from '@liquify/specs'; +import { mdString, settingsType } from 'parse/helpers'; +import { has, path } from 'rambdax'; +import { Complete, SettingsSchema } from 'types'; +import { + CompletionItemKind, + CompletionItem, + SnippetString, + CompletionItemTag, + Position, + Range, + TextEdit, + MarkdownString, + Uri +} from 'vscode'; + +/** + * Get Logical Completions + * + * Generates the logical conditional based completion + * items used within control type tokens. + */ +export function getOperatorCompletions (items: CompletionItem[]): CompletionItem[] { + + return items; + +} + +/** + * Set Object Type + * + * Sets the object completion item kind symbol. + * This is also used for filtering completions so + * otherwise invalid items don't show up at certain entries. + */ +export function getItemKind (type: any): CompletionItemKind { + + if (type === Type.constant) { + return CompletionItemKind.Constant; + } + + if (type === Type.array) { + return CompletionItemKind.Field; + } + + if (type === Type.object) { + return CompletionItemKind.Module; + } + + // @ts-ignore + if (type === Type.data) return CompletionItemKind.Value; + +}; + +export function getDetailName (type: Types.Basic | Type) { + + if (type === Type.array) return 'array'; + else if (type === Type.boolean) return 'boolean'; + else if (type === Type.number) return 'number'; + else if (type === Type.string) return 'start'; + else if (type === Type.object) return 'object'; + else if (type === Type.unknown) return 'unknown'; + + return 'any'; +} + +/** + * Get Schema Completions + * + * Generates the completion items for the `{% schema %}` code block + * region. The items are cherry picked from the JSON Language Service + * parsed JSON content. + */ +export function getSchemaCompletions ( + slice: number, + line: number, + character: number, + items: CompletionItem[] +): CompletionItem[] { + + return items.map(({ + label, + documentation, + textEdit, + kind + }: CompletionItem & { documentation: { value: string } }) => { + + const range = new Range( + new Position(line, character), + new Position(line, textEdit.range.end.character) + ); + + return { + label, + kind, + insertText: new SnippetString(textEdit.newText.slice(slice)), + documentation: mdString(documentation.value), + range: { + inserting: range, + replacing: range + } + }; + + }); + +} + +/** + * Get File Completions + * + * Generates file names of snippets when referencing them via + * `{% render %}` type tags. + */ +export function getFileCompletions (files: Set): CompletionItem[] { + + return Array.from(files).map((file): CompletionItem => { + + const { fsPath } = file; + const label = basename(fsPath, '.liquid'); + const location = slash(fsPath).split('/'); + const filename = location.pop(); + const dirname = location.pop(); + + const documentation = new MarkdownString(`[${label}.liquid](./${label}.liquid)`); + + documentation.baseUri = file; + documentation.supportHtml = true; + + return { + label, + kind: CompletionItemKind.File, + insertText: new SnippetString(label), + preselect: true, + detail: join(dirname, filename), + documentation + }; + + }); + +} + +export function getLocaleSchemaSetting (key: string) { + + const locale: any = null; + + if ($.liquid.files.has('locales_schema')) { + return path(key.slice(2), $.liquid.files.get('locales_schema')); + } + + return locale || key; +} + +/** + * Get Settings Completions + * + * Generates the `settings_schema.json` completions following the + * `settings.*` object. + */ +export function getSettingsCompletions (uri: string, data: SettingsSchema[]) { + + const reference = `[${basename(uri)}](${uri})`; + const objects:{ [prop: string]: IProperty } = {}; + const locale = $.liquid.files.get('locales_schema'); + + for (const setting of data) { + + if (setting.name === 'theme_info') continue; + if (setting.name.startsWith('t:settings_schema.')) { + + const prop = setting.name.slice(18, setting.name.indexOf('.', 18)); + + for (const type of setting.settings) { + if (type?.id && type?.label) { + + const description: string[] = []; + + if (type.label.startsWith('t:')) { + description.push(`**${path(type.label.slice(2), locale) || type.label}**`, '\n'); + } else { + description.push(`**${type.label}**`, '\n'); + } + + if (has('info', type)) { + if (type.info.startsWith('t:')) { + description.push('\n', path(type.info.slice(2), locale), '\n\n'); + } else { + description.push('\n', type.info, '\n\n'); + } + } + + if (type?.default) description.push(`\n\`${type.default}\``, '\n\n'); + + description.push('\n---\n\n', reference); + + objects[type.id] = { + global: true, + scope: 'settings', + type: settingsType(type.type), + summary: `${prop} (${type.type})`, + description: description.join('') + }; + + } + } + + } else { + + for (const type of setting.settings) { + + if (type?.id && type?.label) { + + const description: string[] = []; + + if (type.label.startsWith('t:')) { + description.push(`**${path(type.label.slice(2), locale) || type.label}**`, '\n'); + } else { + description.push(`**${type.label}**`, '\n'); + } + + if (has('info', type)) { + if (type.info.startsWith('t:')) { + description.push('\n', path(type.info.slice(2), locale), '\n\n'); + } else { + description.push('\n', type.info, '\n\n'); + } + } + + if (type?.default) description.push(`\n\`${type.default}\``, '\n\n'); + + description.push('\n---\n\n', reference); + + objects[type.id] = { + global: true, + type: settingsType(type.type), + scope: 'settings', + summary: `${setting.name} (${type.type})`, + description: description.join('') + }; + + } + } + } + } + + liquid.shopify.objects.settings.properties = objects; + + return liquid.shopify.objects; +}; + +/** + * Get Locale Completions + * + * Generates starting property completions for Shopify locales. + * Locales are `key > value` objects. This function will prepare + * the entires for traversal by exposing keys of the locale file. + */ +export function getLocaleCompletions ( + uri: string, + items: { [key: string]: object }, + additionalTextEdits: TextEdit[] = [] +): CompletionItem[] { + + const location = slash(uri).split('/'); + const filename = location.pop(); + const dirname = location.pop(); + const detail = join(dirname, filename); + + return entries(items).map(([ label, props ]): CompletionItem => { + + const object = typeof props === 'object'; + const value = object + ? keys(props).length + : typeof props[label] === 'object' + ? keys(props[label]).length : props; + + const documentation = new MarkdownString(); + + documentation.baseUri = Uri.file(uri); + documentation.supportThemeIcons = true; + documentation.supportHtml = true; + documentation.appendMarkdown(`**${label}**\n\n`); + + if (object) { + documentation.appendMarkdown(`**${value}** available fields\n\n`); + } else { + documentation.appendMarkdown(`*\`${value}\`*\n\n`); + } + + documentation.appendMarkdown(`[${filename}](./${filename})`); + + return { + label, + kind: CompletionItemKind.Module, + detail, + insertText: new SnippetString(label), + additionalTextEdits, + documentation + }; + + }); +}; + +/** + * Get Object Completions + * + * Generates the object completions to be used. This logic is + * partially lifted from the specs. It's a temporary solution + * as this is handled in Liquify. + */ +export function getObjectCompletions (fsPath: string, items: Complete.Items) { + + let template: string; + + const dirn = dirname(fsPath); + const base = basename(fsPath, '.liquid'); + + if (base === 'gift_card' || base === 'robots.txt') { + template = base + '.liquid'; + } else if (dirn === 'customers') { + template = join(dirn, base); + } else { + template = base; + } + + items.delete('objects'); + items.delete('object:template'); + items.delete(`object:${Type.any}`); + items.delete(`object:${Type.array}`); + items.delete(`object:${Type.string}`); + items.delete(`object:${Type.constant}`); + items.delete(`object:${Type.boolean}`); + items.delete(`object:${Type.number}`); + items.delete(`object:${Type.object}`); + + const group = p.ObjectGroups(template, (object: IObject, item: CompletionItem): CompletionItem => { + + if (object.type === Type.object) item.kind = CompletionItemKind.Module; + if (object.const) item.kind = CompletionItemKind.Constant; + + item.tags = object.deprecated ? [ CompletionItemTag.Deprecated ] : []; + item.documentation = mdString(object.description) as any; + + return item; + + }); + + items.set('objects', group.all as any); + items.set('object:template', group.template as any); + items.set(`object:${Type.any}`, group.any as any); + items.set(`object:${Type.array}`, group.array as any); + items.set(`object:${Type.string}`, group.string as any); + items.set(`object:${Type.boolean}`, group.boolean as any); + items.set(`object:${Type.constant}`, group.constant as any); + items.set(`object:${Type.number}`, group.number as any); + items.set(`object:${Type.object}`, group.object as any); + +}; + +/** + * Get Filter Completions + * + * Generates the Filter completions to be used. This logic is + * partially lifted from the specs. It's a temporary solution + * as this is handled in Liquify. + */ +export function getFilterCompletions (items: Filter): CompletionItem[] { + + return entries(items).map(([ + label, + { + description, + deprecated = false, + reference = null, + snippet + } + ]): CompletionItem => { + + const insertText = new SnippetString(snippet || label); + + return { + label, + kind: CompletionItemKind.Value, + tags: deprecated ? [ CompletionItemTag.Deprecated ] : [], + insertText, + preselect: true, + documentation: mdString(description, reference) + }; + }); +} + +/** + * Get Tag Completions + * + * Generates the tag completions to be used. This tag completions + * are generated at runtime and the `items` parameter expects a Liquid + * specification reference structure. + */ +export function getTagCompletions (items: Tags): CompletionItem[] { + + return entries(items).map(([ + label, + { + description, + reference, + deprecated = false, + singleton = false, + snippet = '$1' + } + ]): CompletionItem => { + + const insertText = label === 'else' + ? new SnippetString(` ${label} %}$0`) + : label === 'liquid' + ? new SnippetString(` ${label} ${snippet} %}$0`) + : singleton + ? new SnippetString(` ${label} ${snippet} %}$0`) + : new SnippetString(` ${label} ${snippet} %} $0 {% end${label} %}`); + + return { + label, + kind: CompletionItemKind.Keyword, + tags: deprecated ? [ CompletionItemTag.Deprecated ] : [], + insertText, + preselect: true, + documentation: mdString(description, reference) + }; + + }); + +} diff --git a/extension/data/operators.ts b/extension/data/operators.ts new file mode 100644 index 00000000..5b3690bc --- /dev/null +++ b/extension/data/operators.ts @@ -0,0 +1,67 @@ +import { CompletionItemKind, CompletionItem, SnippetString, MarkdownString } from 'vscode'; + +/** + * Standard Operators + */ +export const StandardOperators: CompletionItem[] = [ + { + label: '==', + kind: CompletionItemKind.Operator, + insertText: new SnippetString('=='), + documentation: new MarkdownString('Equals') + }, + { + label: '!=', + kind: CompletionItemKind.Operator, + insertText: new SnippetString('!='), + documentation: new MarkdownString('Does not equal') + }, + { + label: '>', + kind: CompletionItemKind.Operator, + insertText: new SnippetString('>'), + documentation: new MarkdownString('Greater than') + }, + { + label: '<', + kind: CompletionItemKind.Operator, + insertText: new SnippetString('<'), + documentation: new MarkdownString('Less than') + }, + { + label: '>=', + kind: CompletionItemKind.Operator, + insertText: new SnippetString('>='), + documentation: new MarkdownString('Greater than or equal to') + }, + { + label: '<=', + kind: CompletionItemKind.Operator, + insertText: new SnippetString('<='), + documentation: new MarkdownString('Less than or equal to') + }, + { + label: 'and', + kind: CompletionItemKind.Operator, + insertText: new SnippetString('and'), + documentation: new MarkdownString('Logical and') + }, + { + label: 'or', + kind: CompletionItemKind.Operator, + insertText: new SnippetString('or'), + documentation: new MarkdownString('Logical or') + } +]; + +/** + * Shopify Operators + */ +export const ShopifyOperators: CompletionItem[] = [].concat(StandardOperators, [ + { + label: 'contains', + kind: CompletionItemKind.Operator, + insertText: new SnippetString('contains'), + documentation: new MarkdownString('contains checks for the presence of a substring inside a string and can also check for the presence of a string in an array of strings.\n\n**You cannot use it to check for an object in an array of objects.**') + } +]); diff --git a/extension/data/shared.ts b/extension/data/shared.ts new file mode 100644 index 00000000..126119e4 --- /dev/null +++ b/extension/data/shared.ts @@ -0,0 +1,217 @@ +import { has } from 'rambdax'; +import { basename } from 'node:path'; +import { SchemaBlocks, SchemaSettings, SharedSchema } from 'types'; +import { isArray, isObject } from 'utils'; +import { MarkdownString, Uri } from 'vscode'; +import { JSONSchema } from 'vscode-json-languageservice'; + +/** + * Shared Schema + */ +export function getSharedSchema (uri: Uri, data: SharedSchema, schema: JSONSchema): JSONSchema { + + if (!has('properties', schema.definitions.shared_settings)) { + + schema.definitions.shared_settings = { + type: 'object', + markdownDescription: [ + '**Shared Schema Sections (Settings)**', + '', + 'Inject a [syncify](https://github.com/panoply/syncify) shared schema reference (`$ref`).' + ].join('\n'), + properties: { + $ref: { + anyOf: [] + } + } + }; + } + + if (!has('properties', schema.definitions.shared_blocks)) { + + schema.definitions.shared_blocks = { + type: 'object', + markdownDescription: [ + '**Shared Schema Sections (Blocks)**', + '', + 'Inject a [syncify](https://github.com/panoply/syncify) shared schema reference (`$ref`).' + ].join('\n'), + properties: { + $ref: { + anyOf: [] + } + } + }; + } + + const $ref: { + settings: { + $comment: string; + type: string; + enum: string[]; + markdownEnumDescriptions: string[]; + }, + blocks: { + $comment: string; + type: string; + enum: string[]; + markdownEnumDescriptions: string[]; + } + } = { + blocks: { + $comment: uri.fsPath, + type: 'string', + enum: [], + markdownEnumDescriptions: [] + }, + settings: { + $comment: uri.fsPath, + type: 'string', + enum: [], + markdownEnumDescriptions: [] + } + }; + + const filename = basename(uri.fsPath, '.schema'); + + if (data === null) { + + const settings = schema.definitions.shared_settings.properties.$ref as { anyOf: typeof $ref.settings[] }; + const settingsIndex = settings.anyOf.findIndex(({ $comment }) => $comment === uri.fsPath); + + if (settingsIndex > -1) settings.anyOf.splice(settingsIndex, 1); + + const blocks = schema.definitions.shared_blocks.properties.$ref as { anyOf: typeof $ref.blocks[] }; + const blocksIndex = blocks.anyOf.findIndex(({ $comment }) => $comment === uri.fsPath); + + if (blocksIndex > -1) blocks.anyOf.splice(blocksIndex, 1); + + return schema; + + } + + for (const prop in data) { + + if (prop === '$schema' || prop === '$description') continue; + + /** + * The $ref entry property + */ + const name = `${filename}.${prop}`; + + /** + * The structure type + */ + const md = new MarkdownString(); + + /** + * The defintion type to inject within + */ + let type: 'blocks' | 'settings'; + + if (isArray>(data[prop])) { + + // Block Spread + // + if (has('settings', data[prop][0])) { + + type = 'blocks'; + + md.appendMarkdown('**Block Spread**\n\n') + .appendMarkdown('This `$ref` contains a list of block settings available for section `blocks[]`') + .appendMarkdown('\n\n---\n'); + + } else { + + type = 'settings'; + + md.appendMarkdown('**Setting Spread**\n\n') + .appendMarkdown('This `$ref` contains a list of input settings available to `settings[]` or `blocks[]`') + .appendMarkdown('\n\n---\n'); + } + + if (has('$description', data[prop][0])) { + if (isArray(data[prop][0].$description)) { + md.appendMarkdown(data[prop][0].$description.join('\n') + '\n'); + } else { + md.appendMarkdown(data[prop][0].$description + '\n'); + } + } + + } else if (isObject(data[prop])) { + + if (has('settings', data[prop])) { + + if (has('type', data[prop]) && has('name', data[prop])) { + + type = 'blocks'; + + md.appendMarkdown('**Block Singleton**\n\n') + .appendMarkdown('This `$ref` contains a single block settings available to section `blocks[]`') + .appendMarkdown('\n\n---\n'); + } else { + + type = 'settings'; + + md.appendMarkdown('**Setting Group**\n\n') + .appendMarkdown('This `$ref` contains a settings group available for `settings[]`') + .appendMarkdown('\n\n---\n'); + } + } else { + + type = 'settings'; + + md.appendMarkdown('**Setting Singleton**\n\n') + .appendMarkdown('This `$ref` contains a single input setting available to `settings[]`') + .appendMarkdown('\n\n---\n'); + } + + if (has('$description', data[prop])) { + // @ts-ignore + if (isArray(data[prop].$description)) { + + // @ts-ignore + md.appendMarkdown(data[prop].$description.join('\n') + '\n'); + } else { + + // @ts-ignore + md.appendMarkdown(data[prop].$description + '\n'); + } + } + } + + md.appendMarkdown(`\n[${filename}.schema](./${filename}.schema)`); + md.baseUri = uri; + + $ref[type].enum.push(name); + $ref[type].markdownEnumDescriptions.push(md.value); + + } + + if ($ref.settings.enum.length > 0) { + + const settings = schema.definitions.shared_settings.properties.$ref as { anyOf: typeof $ref.settings[] }; + const index = settings.anyOf.findIndex(({ $comment }) => $comment === uri.fsPath); + + if (index > -1) { + settings.anyOf[index] = $ref.settings; + } else { + settings.anyOf.push($ref.settings); + } + } + + if ($ref.blocks.enum.length > 0) { + + const blocks = schema.definitions.shared_blocks.properties.$ref as { anyOf: typeof $ref.blocks[] }; + const index = blocks.anyOf.findIndex(({ $comment }) => $comment === uri.fsPath); + + if (index > -1) { + blocks.anyOf[index] = $ref.blocks; + } else { + blocks.anyOf.push($ref.blocks); + } + } + + return schema; + +} diff --git a/extension/data/store.ts b/extension/data/store.ts new file mode 100644 index 00000000..8f265790 --- /dev/null +++ b/extension/data/store.ts @@ -0,0 +1,1909 @@ +/** +* LICENSE - THIS FILE IS NOT MIT +* +* THIS LICENCE IS STRICTLY IMPOSED AND IS LIMITED TO APPROVED USAGE +* YOU ARE NOT PERMITTED TO USE THE CONTENTS OF THIS FILE IN ANY FORM +* YOUR ARE NOT AUTHORISED TO COPY, RE-DISTRIBUTE OR MODIFY THIS FILE +* ASK PERMISSION FROM THE PROJECT AUTHOR BEFORE USAGE OR RE-PURPOSING +* +*/ +/* eslint-disable */ +export const schema = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "shopify-section-schema", + "version": 1.1, + "definitions": { + "shared_settings": { + "$comment": "Shared Section Settings $ref (references) will be inserted here" + }, + "shared_blocks": { + "$comment": "Shared Section Blocks $ref (references) will be inserted here" + }, + "languages": { + "type": "object", + "markdownDescription": "Locales", + "properties": { + "af": { + "type": "string", + "markdownDescription": "Afrikaans (Afrikaans)" + }, + "ar": { + "type": "string", + "markdownDescription": "Arabic (العربية)" + }, + "az": { + "type": "string", + "markdownDescription": "Azerbaijani (Azərbaycanlı)" + }, + "be": { + "type": "string", + "markdownDescription": "Belarusian (беларуская)" + }, + "bg": { + "type": "string", + "markdownDescription": "Bulgarian (български)" + }, + "bs": { + "type": "string", + "markdownDescription": "Bosnian (bosanski/босански)" + }, + "cs": { + "type": "string", + "markdownDescription": "Czech (čeština)" + }, + "cy": { + "type": "string", + "markdownDescription": "Welsh (Cymraeg)" + }, + "da": { + "type": "string", + "markdownDescription": "Danish (dansk)" + }, + "de": { + "type": "string", + "markdownDescription": "German (Deutsch)" + }, + "el": { + "type": "string", + "markdownDescription": "Greek (ελληνικά)" + }, + "en": { + "type": "string", + "markdownDescription": "English" + }, + "es": { + "type": "string", + "markdownDescription": "Spanish (español)" + }, + "et": { + "type": "string", + "markdownDescription": "Estonian (eesti)" + }, + "fa": { + "type": "string", + "markdownDescription": "Persian (فارسى)" + }, + "fi": { + "type": "string", + "markdownDescription": "Finnish (suomi)" + }, + "fr": { + "type": "string", + "markdownDescription": "French (français)" + }, + "fy": { + "type": "string", + "markdownDescription": "Frisian (Frysk)" + }, + "he": { + "type": "string", + "markdownDescription": "Hebrew (עברית)" + }, + "hr": { + "type": "string", + "markdownDescription": "Croatian (hrvatski)" + }, + "hi": { + "type": "string", + "markdownDescription": "Hindi (हिंदी)" + }, + "hu": { + "type": "string", + "markdownDescription": "Hungarian (magyar)" + }, + "id": { + "type": "string", + "markdownDescription": "Indonesian (Bahasa Indonesia)" + }, + "is": { + "type": "string", + "markdownDescription": "Icelandic (íslenska)" + }, + "lv": { + "type": "string", + "markdownDescription": "Latvian (latviešu)" + }, + "nl": { + "type": "string", + "markdownDescription": "Dutch (Nederlands)" + }, + "no": { + "type": "string", + "markdownDescription": "Norwegian (norsk)" + }, + "it": { + "type": "string", + "markdownDescription": "Italian (italiano)" + }, + "ja": { + "type": "string", + "markdownDescription": "Japanese (日本語)" + }, + "ko": { + "type": "string", + "markdownDescription": "Korean (한국어/韓國語)" + }, + "lb": { + "type": "string", + "markdownDescription": "Luxembourgish (Lëtzebuergesch)" + }, + "lt": { + "type": "string", + "markdownDescription": "Lithuanian (lietuvių)" + }, + "mk": { + "type": "string", + "markdownDescription": "Macedonian (македонски јазик)" + }, + "pa": { + "type": "string", + "markdownDescription": "Punjabi (ਪੰਜਾਬੀ)" + }, + "pl": { + "type": "string", + "markdownDescription": "Polish (polski)" + }, + "pt": { + "type": "string", + "markdownDescription": "Portuguese (Português)" + }, + "ro": { + "type": "string", + "markdownDescription": "Romanian (română)" + }, + "ru": { + "type": "string", + "markdownDescription": "Russian (русский)" + }, + "si": { + "type": "string", + "markdownDescription": "Sinhala (සිංහල)" + }, + "sl": { + "type": "string", + "markdownDescription": "Albanian (shqipe)" + }, + "sk": { + "type": "string", + "markdownDescription": "Slovak (slovenčina)" + }, + "sq": { + "type": "string", + "markdownDescription": "Slovenian (slovenski)" + }, + "sv": { + "type": "string", + "markdownDescription": "Swedish (svenska)" + }, + "ta": { + "type": "string", + "markdownDescription": "Tamil (தமிழ்)" + }, + "th": { + "type": "string", + "markdownDescription": "Thai (ไทย)" + }, + "tr": { + "type": "string", + "markdownDescription": "Turkish (Türkçe)" + }, + "zh": { + "type": "string", + "markdownDescription": "Chinese (中文)" + }, + "uk": { + "type": "string", + "markdownDescription": "Ukrainian (українська)" + }, + "vi": { + "type": "string", + "markdownDescription": "Vietnamese (Tiếng Việt/㗂越)" + } + } + }, + "content": { + "required": [ + "content" + ], + "type": "object", + "properties": { + "content": { + "title": "Content", + "markdownDescription": "The setting content, which will show in the theme editor.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#paragraph)", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/languages" + } + ] + } + } + }, + "header": { + "required": [ + "content" + ], + "type": "object", + "properties": { + "content": { + "title": "Content", + "markdownDescription": "The setting content, which will show in the theme editor.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#standard-attributes)" + }, + "info": { + "title": "Info", + "markdownDescription": "In addition to the standard attributes of a sidebar setting, `header` type can accept informational text.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#header)", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/languages" + } + ] + } + } + }, + "placeholder": { + "type": "object", + "properties": { + "placeholder": { + "title": "Placeholder", + "markdownDescription": "A placeholder value", + "default": "", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/languages" + } + ] + } + } + }, + "limit": { + "type": "object", + "properties": { + "limit": { + "type": "number", + "title": "Limit", + "markdownDescription": "The maximum number of items that the merchant can select. The default limit, and the maximum limit you can set, is 50.", + "maximum": 50 + } + } + }, + "select": { + "required": [ + "options" + ], + "properties": { + "options": { + "type": "array", + "title": "Select Options", + "markdownDescription": "Takes an array of value/label definitions for each option in the drop-down", + "items": { + "type": "object", + "required": [ + "value", + "label" + ], + "additionalProperties": false, + "defaultSnippets": [ + { + "label": "Options", + "markdownDescription": "Takes an array of value/label definitions for each option in the drop-down.", + "body": { + "value": "$1", + "label": "${1/([^_]+)(_*)/${1:/capitalize}${2:+ }/g}$2" + } + }, + { + "label": "Options with group", + "markdownDescription": "Takes an array of value/label definitions for each option in the drop-down with an optional attribute you can add to each option to create option groups in the drop-down", + "body": { + "value": "$1", + "label": "${1/([^_]+)(_*)/${1:/capitalize}${2:+ }/g}$2", + "group": "$3" + } + } + ], + "properties": { + "value": { + "type": "string", + "markdownDescription": "The value of the select options. This will be used as the output" + }, + "label": { + "title": "Label", + "markdownDescription": "A label to render to the theme editor", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/languages" + } + ] + }, + "group": { + "title": "Group", + "markdownDescription": "An optional attribute you can add to each option to create option groups in the drop-down.", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/languages" + } + ] + } + } + } + } + } + }, + "range": { + "required": [ + "min", + "max", + "step" + ], + "type": "object", + "properties": { + "step": { + "type": "number", + "title": "Step", + "minimum": 0.1, + "default": 1, + "markdownDescription": "The step refers to the step count for the slider values. For example, if you set the step to 5, then the range slider will count by fives. By default, the step is set to 1." + }, + "min": { + "type": "number", + "title": "Min", + "markdownDescription": "The minimum number of steps" + }, + "max": { + "type": "number", + "title": "Max", + "markdownDescription": "The maximum number of steps" + }, + "unit": { + "title": "Unit", + "markdownDescription": "The unit of measure label. For example, you could use sec for seconds, or px for pixels.", + "default": "", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/languages" + } + ] + } + } + }, + "radio": { + "required": [ + "options" + ], + "type": "object", + "properties": { + "options": { + "type": "array", + "title": "Radio Options", + "markdownDescription": "Takes an array of value/label definitions", + "items": { + "type": "object", + "required": [ + "value", + "label" + ], + "additionalProperties": false, + "defaultSnippets": [ + { + "label": "Radio Options", + "markdownDescription": "Value and label definitions", + "body": { + "value": "$1", + "label": "${2/([^_]+)(_*)/${1:/capitalize}${2:+ }/g}$3" + } + } + ], + "properties": { + "value": { + "type": "string" + }, + "label": { + "title": "Label", + "markdownDescription": "Radio Label", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/languages" + } + ] + } + } + } + } + } + }, + "color": { + "type": "object", + "format": "color", + "properties": { + "default": { + "type": "string", + "format": "color" + } + } + }, + "video_url": { + "required": [ + "accepts" + ], + "type": "object", + "properties": { + "accept": { + "type": "array", + "title": "Accept", + "uniqueItems": true, + "additionalItems": false, + "markdownDescription": "Takes an array of accepted video providers. Valid values are youtube, vimeo, or both", + "items": { + "type": "string", + "enum": [ + "youtube", + "vimeo" + ] + } + }, + "placeholder": { + "$ref": "#/definitions/languages" + } + } + }, + "settings": { + "$comment": "Shared Section Schema $ref (references) and inserted dynamically via the JSON language server. When \"schema\" file globs are provided in liquidrc or workspace settings, the vscode extension will insert the additional schemas required", + "type": "array", + "markdownDescription": "You can create section specific [settings](https://shopify.dev/themes/architecture/settings/input-settings) to allow merchants to customize the section with the `settings` object:\n\n**Example**\n\n```liquid\n\n{% schema %}\n{\n \"name\": \"Slideshow\",\n \"tag\": \"section\",\n \"class\": \"slideshow\",\n \"settings\": [\n {\n \"type\": \"text\",\n \"id\": \"header\",\n \"label\": \"Header\"\n }\n ]\n}\n{% endschema %}\n\n```\n\n**[Input Settings](https://shopify.dev/themes/architecture/settings/input-settings)**\n\nInput settings are generally composed of [standard attributes](https://shopify.dev/themes/architecture/settings/input-settings#standard-attributes), and there are two categories:\n\n- [Basic input settings](https://shopify.dev/themes/architecture/settings/input-settings#basic-input-settings)\n- [Specialized input settings](https://shopify.dev/themes/architecture/settings/input-settings#specialized-input-settings)\n\nTo learn how to access the values of these settings for use in your theme, refer to the [settings overview](https://shopify.dev/themes/architecture/settings#access-settings).\n\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings)\n", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "title": "Type", + "required": [ + "type" + ], + "markdownDescription": "The setting type, which can be any of the [basic](https://shopify.dev/docs/themes/architecture/settings/input-settings#basic-input-settings) or [specialized](https://shopify.dev/docs/themes/architecture/settings/input-settings#specialized-input-settings) input setting types.\n\n**Basic input settings**\n\nAnchor link to section titled \"Basic input settings\" The following are the basic input setting types:\n\n- [checkbox](https://shopify.dev/docs/themes/architecture/settings/input-settings#checkbox)\n- [number](https://shopify.dev/docs/themes/architecture/settings/input-settings#number)\n- [radio](https://shopify.dev/docs/themes/architecture/settings/input-settings#radio)\n- [range](https://shopify.dev/docs/themes/architecture/settings/input-settings#range)\n- [select](https://shopify.dev/docs/themes/architecture/settings/input-settings#select)\n- [text](https://shopify.dev/docs/themes/architecture/settings/input-settings#text)\n- [textarea](https://shopify.dev/docs/themes/architecture/settings/input-settings#textarea)\n\n\n**Specialized input settings**\n\nAnchor link to section titled \"Specialized input settings\" The following are the specialized input setting types:\n\n- [article](https://shopify.dev/docs/themes/architecture/settings/input-settings#article)\n- [blog](https://shopify.dev/docs/themes/architecture/settings/input-settings#blog)\n- [collection](https://shopify.dev/docs/themes/architecture/settings/input-settings#collection)\n- [collection_list](https://shopify.dev/docs/themes/architecture/settings/input-settings#collection_list)\n- [color](https://shopify.dev/docs/themes/architecture/settings/input-settings#color)\n- [color_background](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_background)\n- [color_scheme](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme)\n- [color_scheme_group](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme_group)\n- [font_picker](https://shopify.dev/docs/themes/architecture/settings/input-settings#font_picker)\n- [html](https://shopify.dev/docs/themes/architecture/settings/input-settings#html)\n- [image_picker](https://shopify.dev/docs/themes/architecture/settings/input-settings#image_picker)\n- [inline_richtext](https://shopify.dev/docs/themes/architecture/settings/input-settings#inline_richtext)\n- [link_list](https://shopify.dev/docs/themes/architecture/settings/input-settings#link_list)\n- [liquid](https://shopify.dev/docs/themes/architecture/settings/input-settings#liquid)\n- [page](https://shopify.dev/docs/themes/architecture/settings/input-settings#page)\n- [product](https://shopify.dev/docs/themes/architecture/settings/input-settings#product)\n- [product_list](https://shopify.dev/docs/themes/architecture/settings/input-settings#product_list)\n- [richtext](https://shopify.dev/docs/themes/architecture/settings/input-settings#rich_text)\n- [url](https://shopify.dev/docs/themes/architecture/settings/input-settings#url)\n- [video](https://shopify.dev/docs/themes/architecture/settings/input-settings#video)\n- [video_url](https://shopify.dev/docs/themes/architecture/settings/input-settings#video_url)\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/sections/section-schema#default)\n", + "oneOf": [ + { + "enum": [ + "text" + ], + "title": "Single-line text fields", + "markdownDescription": "A setting of type `text` outputs a single-line text field. In addition to the [standard attributes](https://shopify.dev/themes/architecture/settings/input-settings#standard-attributes) of an input setting.\n\nWhen accessing the value of a `text` type setting, data is returned as one of the following:\n\n- A [string](https://shopify.dev/api/liquid/basics#types).\n- An [empty object](https://shopify.dev/api/liquid/basics#empty), if nothing has been entered.\n\n#\n\n---\n\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#text)\n\n\n" + }, + { + "enum": [ + "textarea" + ], + "title": "Multi-line text areas", + "markdownDescription": "A setting of type `textarea` outputs a multi-line text field. In addition to the [standard attributes](https://shopify.dev/themes/architecture/settings/input-settings#standard-attributes) of an input setting.\n\nWhen accessing the value of a `textarea` type setting, data is returned as one of the following:\n\n- A [string](https://shopify.dev/api/liquid/basics#types).\n- An [empty object](https://shopify.dev/api/liquid/basics#empty), if nothing has been entered.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#textarea)\n\n\n" + }, + { + "enum": [ + "image_picker" + ], + "title": "Image Picker", + "markdownDescription": "A setting of type `image_picker` outputs an image picker field that's automatically populated with the available images from the [Files](https://help.shopify.com/en/manual/shopify-admin/productivity-tools/file-uploads) section of Shopify admin, and has the option to upload new images. Merchants also have an opportunity to enter alt text and select a [focal point](https://shopify.dev/themes/architecture/settings/input-settings#image-focal-points) for their image.\n\nYou can use these fields to capture an image selection, such as logos, favicons, and slideshow images.\n\nWhen accessing the value of an `image_picker` type setting, data is returned as one of the following:\n\n- An [image object](https://shopify.dev/api/liquid/objects/image).\n- [nil](https://shopify.dev/api/liquid/basics/types#nil), if either no selection has been made or the selection no longer exists.\n\n> **NOTE**\n>\n> Settings of type `image_picker` are not updated when switching presets. `image_picker` settings also don't support the `default` attribute.\n\n**[Image focal points](https://shopify.dev/themes/architecture/settings/input-settings#image-focal-points)**\n\nImages selected using an `image_picker` setting support focal points. A focal point is a position in an image that the merchant wants to remain in view as the image is cropped and adjusted by the theme. Focal points can be set in the theme editor `image_picker` setting, or from the Files page.\n\nTo make sure that your theme respects the focal point of the image, do the following:\n\n- Render your images using the [image_tag](https://shopify.dev/api/liquid/filters/image_tag) filter.\n- Consider positioning images within containers using `object-fit: cover`.\n\nUsing `image_tag`, if a focal point was provided, then an `object-position` style is added to the image tag, with the value set to the focal point.\n\n**Input**\n\n```liquid\n\n{{ section.settings.image_with_text_image | image_url: width: 1500 | image_tag }}\n\n```\n\n**Output**\n\n```html\n\n\"My\n\n\n```\n\nIf you need override the `object-position` style for a specific use case, then pass a style: `object-position: inherit;` property to the `image_tag` filter.\n\n> **TIP**\n>\n> You can also access the focal point data using [image.presentation.focal_point](https://shopify.dev/api/liquid/objects/image_presentation#image_presentation-focal_point).\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#image_picker)\n" + }, + { + "enum": [ + "radio" + ], + "title": "Radio Button", + "markdownDescription": "A setting of type `radio` outputs a radio option field. In addition to the [standard attributes](https://shopify.dev/themes/architecture/settings/input-settings#standard-attributes) of an input setting, `radio` type settings have a required options attribute that accepts an array of value and label definitions.\n\nYou can use these fields to capture a multi-option selection, such as the alignment of a header logo.\n\nWhen accessing the value of a radio type setting, data is returned as a [string](https://shopify.dev/api/liquid/basics/types#string).\n\n> **NOTE**\n>\n> If `default` is unspecified, then the first option is selected by default.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#radio)\n\n" + }, + { + "enum": [ + "select" + ], + "title": "Selection drop-down", + "markdownDescription": "A setting of type `select` outputs a drop-down selector field. In addition to the [standard attributes](https://shopify.dev/themes/architecture/settings/input-settings#standard-attributes) of an input setting.\n\nWhen accessing the value of a `select` type setting, data is returned as a [string](https://shopify.dev/api/liquid/basics/types#string).\n\n> **NOTE**\n>\n> If `default` is unspecified, then the first option is selected by default.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#select)\n\n\n" + }, + { + "enum": [ + "checkbox" + ], + "title": "Checkbox", + "markdownDescription": "A setting of type `checkbox` outputs a checkbox field. These fields can be used for toggling features on and off, such as whether to show an announcement bar.\n\nWhen accessing the value of a `checkbox` type setting, data is returned as a [boolean](https://shopify.dev/api/liquid/basics/types#boolean).\n\n> **NOTE**\n>\n> If `default` is unspecified, then the value is false by default.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#checkbox)\n\n" + }, + { + "enum": [ + "number" + ], + "title": "Number", + "markdownDescription": "A setting of type `number` outputs a single number field. In addition to the [standard attributes](https://shopify.dev/themes/architecture/settings/input-settings#standard-attributes) of an input setting.\n\nWhen accessing the value of a `number` type setting, data is returned as one of the following:\n\n- A [number](https://shopify.dev/api/liquid/basics/types#number).\n- [nil](https://shopify.dev/api/liquid/basics/types#nil), if nothing has been entered.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#number)\n\n\n" + }, + { + "enum": [ + "range" + ], + "title": "Range", + "markdownDescription": "A setting of type `range` outputs a range slider field. In addition to the [standard attributes](https://shopify.dev/themes/architecture/settings/input-settings#standard-attributes) of an input setting.\n\nWhen accessing the value of a `range` type setting, data is returned as a [number](https://shopify.dev/api/liquid/basics/types#number).\n\n> **CAUTION**\n>\n> The `default` attribute is required. The `min`, `max`, `step`, and `default` attributes can't be string values. Failing to adhere results in an error.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#range)\n\n\n" + }, + { + "enum": [ + "color" + ], + "title": "Color Picker", + "markdownDescription": "A setting of type `color` outputs a color picker field. You can use these fields to capture a color selection for various theme elements, such as the body text color.\n\nWhen accessing the value of a color type setting, data is returned as one of the following:\n\n- A [color object](https://shopify.dev/api/liquid/objects/color).\n- `blank`, if no selection has been made.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#color)\n" + }, + { + "enum": [ + "color_scheme" + ], + "title": "Color Scheme", + "markdownDescription": "A setting of type `color_scheme` outputs a picker with all of the available theme color schemes, and a preview of the selected color scheme. Theme color schemes in the picker are defined using the [color_scheme_group](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme_group) setting. You can apply a color scheme to sections, blocks and general theme settings. Color scheme settings aren't supported in app blocks.\n\nFor example, the following setting generates the following output:\n\n```json\n{\n \"type\": \"color_scheme\",\n \"id\": \"color_scheme\",\n \"default\": \"scheme_1\",\n \"label\": \"Color Scheme\"\n}\n```\n\nWhen accessing the value of a `color_scheme` type setting, Shopify returns the selected `color_scheme` object from `color_scheme_group`.\n\nIf no value was entered, or the value was invalid, then the default value from `color_scheme` is returned. If the default value is also invalid, then the first `color_scheme` from `color_scheme_group` is returned.\n\nIf the theme doesn't have `color_scheme_group` data in `settings_data.json`, then [nil](https://shopify.dev/docs/api/liquid/basics/types#nil) is returned.\n\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme)\n" + }, + { + "enum": [ + "color_background" + ], + "title": "Color Background", + "markdownDescription": "A setting of type `color_background` outputs a text field for entering [CSS background](https://developer.mozilla.org/en-US/docs/Web/CSS/background) properties. You can use these fields to capture background settings for various theme elements, such as the store background.\n\n> **CAUTION**\n>\n> A Settings of type `color_background` do not support image related background properties.\n\nWhen accessing the value of a `color_background` type setting, data is returned as one of the following:\n\n- A [string](https://shopify.dev/api/liquid/basics/types#string).\n- [nil](https://shopify.dev/api/liquid/basics/types#nil), if nothing has been entered.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#color_background)\n" + }, + { + "enum": [ + "font_picker" + ], + "title": "Font Picker", + "markdownDescription": "A setting of type `font_picker` outputs a font picker field that's automatically populated with fonts from the [Shopify font library](https://shopify.dev/themes/architecture/settings/fonts#shopify-font-library). This library includes web-safe fonts, a selection of Google Fonts, and fonts licensed by Monotype.\n\nYou can use these fields to capture a font selection for various theme elements, such as the base heading font.\n\nWhen accessing the value of a `font_picker` type setting, data is returned as a [font object](https://shopify.dev/api/liquid/objects/font).\n\n> **CAUTION**\n>\n> The `default` attribute is required. Failing to include it will result in an error. You can find the possible values through the [available fonts](https://shopify.dev/themes/architecture/settings/fonts#available-fonts) in the Shopify font library.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#font_picker)\n" + }, + { + "enum": [ + "collection" + ], + "title": "Collections drop-down", + "markdownDescription": "A setting of type `collection` outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture a collection selection, such as a collection for featuring products on the homepage.\n\nWhen accessing the value of a `collection` type setting, data is returned as one of the following\n\n- A [collection object](https://shopify.dev/api/liquid/objects/collection).\n To ensure backwards compatibility with [legacy resource-based settings](https://shopify.dev/themes/architecture/settings#legacy-resource-based-settings), outputting the setting directly will return the object's handle.\n- `blank` if no selection has been made, the selection isn't visible, or the selection no longer exists.\n\n> **NOTE**\n>\n> Settings of type `collection` are not updated when switching presets. `collection` settings also don't support the `default` attribute.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#collection)\n" + }, + { + "enum": [ + "collection_list" + ], + "title": "Collection List", + "markdownDescription": "A setting of type `collection_list` outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture multiple collections, such as a group of collections to feature on the homepage.\n\nWhen accessing the value of a `collection_list` type setting, data is returned as one of the following:\n\n- An array of [collection objects](https://shopify.dev/api/liquid/objects/collection).\n This array supports pagination using the [paginate](https://shopify.dev/api/liquid/tags/paginate#paginate-paginating-setting-arrays) tag. You can also append `.count` to the [setting key](https://shopify.dev/themes/architecture/settings#access-settings) to return the number of collections in the array.\n- `blank` if no selection has been made, the selection isn't visible, or the selection no longer exists.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#collection_list)\n" + }, + { + "enum": [ + "product" + ], + "title": "Products drop-down", + "markdownDescription": "A setting of type `product` outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture a product selection, such as the product to feature on the homepage.\n\n- A [product object](https://shopify.dev/api/liquid/objects/product).\n To ensure backwards compatibility with [legacy resource-based settings](https://shopify.dev/themes/architecture/settings#legacy-resource-based-settings), outputting the setting directly will return the object's handle.\n- `blank`, if no selection has been made, the selection isn't visible, or the selection no longer exists.\n\n> **NOTE**\n>\n> Settings of type product are not updated when switching presets. `product` settings also don't support the `default` attribute.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#product)\n" + }, + { + "enum": [ + "product_list" + ], + "title": "Products drop-down", + "markdownDescription": "A setting of type `product_list` outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture multiple products, such as a group of products to feature on the homepage.\n\nWhen accessing the value of a `product_list` type setting, data is returned as one of the following:\n\n- An array of [product objects](https://shopify.dev/api/liquid/objects/product).\n This array supports pagination using the [paginate](https://shopify.dev/api/liquid/tags/paginate#paginate-paginating-setting-arrays) tag. You can also append `.count` to the [setting key](https://shopify.dev/themes/architecture/settings#access-settings) to return the number of products in the array.\n- `blank` if no selection has been made, the selection isn't visible, or the selection no longer exists.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#product_list)\n\n" + }, + { + "enum": [ + "blog" + ], + "title": "Blogs drop-down", + "markdownDescription": "A setting of type `blog` outputs a blog picker field that's automatically populated with the available blogs for the store. You can use these fields to capture a blog selection, such as the blog to feature in the sidebar.\n\nWhen accessing the value of a `blog` type setting, data is returned as one of the following\n\n- A [blog object](https://shopify.dev/api/liquid/objects/blog).\n To ensure backwards compatibility with [legacy resource-based settings](https://shopify.dev/themes/architecture/settings#legacy-resource-based-settings), outputting the setting directly will return the object's handle.\n- `blank` if no selection has been made, the selection isn't visible, or the selection no longer exists.\n\n> **NOTE**\n>\n> Settings of type `blog` are not updated when switching presets. `blog` settings also don't support the `default` attribute.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#blog)\n" + }, + { + "enum": [ + "page" + ], + "title": "Pages drop-down", + "markdownDescription": "A setting of type `page` outputs a page picker field that's automatically populated with the available pages for the store. You can use these fields to capture a page selection, such as the page to feature content for in a size-chart display.\n\nWhen accessing the value of a `page` type setting, data is returned as one of the following:\n\n- A [page object](https://shopify.dev/api/liquid/objects/page).\n To ensure backwards compatibility with [legacy resource-based settings](https://shopify.dev/themes/architecture/settings#legacy-resource-based-settings), outputting the setting directly will return the object's handle.\n- `blank`, if no selection has been made, the selection isn't visible, or the selection no longer exists.\n\n> **NOTE**\n>\n> Settings of type page are not updated when switching presets. `page` settings also don't support the `default` attribute.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#page)\n\n" + }, + { + "enum": [ + "link_list" + ], + "title": "Link lists drop-down", + "markdownDescription": "A setting of type `link_list` outputs a menu picker field that's automatically populated with the available menus for the store. You can use these fields to capture a menu selection, such as the menu to use for footer links.\n\nWhen accessing the value of a link_list type setting, data is returned as one of the following:\n\n- A [linklist object](https://shopify.dev/api/liquid/objects/linklist).\n- `blank`, if either no selection has been made or the selection no longer exists.\n\n> **NOTE**\n>\n> Accepted values for the `default` attribute are `main-menu` and `footer`.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#link_list)\n" + }, + { + "enum": [ + "url" + ], + "title": "URL", + "markdownDescription": "A setting of type `url` outputs a URL entry field where you can manually enter external URLs and relative paths. It also has a picker that's automatically populated with the following available resources for the shop:\n\n- Articles\n- Blogs\n- Collections\n- Pages\n- Products\n\n> _You can use these fields to capture a URL selection, such as the URL to use for a slideshow button link._\n\nWhen accessing the value of a url type setting, data is returned as one of the following:\n\n- A [string](https://shopify.dev/api/liquid/basics/types#string) that contains the selected URL.\n- [nil](https://shopify.dev/api/liquid/basics/types#nil), if nothing has been entered.\n\n> **NOTE**\n>\n> Accepted values for the `default` attribute are `/collections` and `/collections/all`.\n\n#\n\n---\n\n[Shopify Documentation](https://shopify.dev/themes/architecture/settings/input-settings#url)\n" + }, + { + "enum": [ + "richtext" + ], + "title": "RichText", + "markdownDescription": "A setting of type `richtext` outputs a multi-line text field with the following basic formatting options:\n\n- Bold\n- Italic\n- Underline\n- Link\n- Paragraph\n- Unordered list\n\n> **NOTE**\n>\n> No underline option appears in the rich text component. Merchants can underline text using the `CMD`+`U` or `CTRL`+`U` keyboard shortcut.\n\nYou can use these fields to capture formatted text content, such as introductory brand content on the homepage.\n\nWhen accessing the value of a richtext type setting, data is returned as one of the following:\n\n- A [string](https://shopify.dev/api/liquid/basics/types#string) that contains the entered content.\n- An [empty object](https://shopify.dev/api/liquid/basics/types#emptydrop), if nothing has been entered.\n\n**[default](https://shopify.dev/themes/architecture/settings/input-settings#default)**\n\nThe `default` attribute isn't required. However, if it's used, then only `

` or `