diff --git a/.changeset/four-mangos-burn.md b/.changeset/four-mangos-burn.md new file mode 100644 index 000000000..629142b43 --- /dev/null +++ b/.changeset/four-mangos-burn.md @@ -0,0 +1,6 @@ +--- +"@codeimage/api": minor +"@codeimage/app": minor +--- + +feat(app/api): allow to disable ligatures in fonts #483 diff --git a/apps/api/prisma/migrations/20230302182841_project_unique_owner_constraint_336_feature/migration.sql b/apps/api/prisma/migrations/20230302182841_project_unique_owner_constraint_336_feature/migration.sql new file mode 100644 index 000000000..10e888016 --- /dev/null +++ b/apps/api/prisma/migrations/20230302182841_project_unique_owner_constraint_336_feature/migration.sql @@ -0,0 +1,5 @@ +-- AddForeignKey +ALTER TABLE "Project" ADD CONSTRAINT "Project_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- RenameIndex +ALTER INDEX "Project_id_ownerId_idx" RENAME TO "Project_id_ownerId_key"; diff --git a/apps/api/prisma/migrations/20230302183231_editor_add_ligatures_option/migration.sql b/apps/api/prisma/migrations/20230302183231_editor_add_ligatures_option/migration.sql new file mode 100644 index 000000000..6c48cf0d4 --- /dev/null +++ b/apps/api/prisma/migrations/20230302183231_editor_add_ligatures_option/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "SnippetEditorOptions" ADD COLUMN "enableLigatures" BOOLEAN NOT NULL DEFAULT true; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 0e9a92b57..a8170e42c 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -70,6 +70,7 @@ model SnippetEditorOptions { fontWeight Int @default(400) showLineNumbers Boolean @default(false) themeId String + enableLigatures Boolean @default(true) } model SnippetEditorTab { diff --git a/apps/api/src/modules/project/infra/prisma/prisma-project.repository.ts b/apps/api/src/modules/project/infra/prisma/prisma-project.repository.ts index 9853239e8..88a1dbff5 100644 --- a/apps/api/src/modules/project/infra/prisma/prisma-project.repository.ts +++ b/apps/api/src/modules/project/infra/prisma/prisma-project.repository.ts @@ -162,6 +162,7 @@ export function makePrismaProjectRepository( fontWeight: data.editorOptions.fontWeight, showLineNumbers: data.editorOptions.showLineNumbers, themeId: data.editorOptions.themeId, + enableLigatures: data.editorOptions.enableLigatures, }, }, frame: { diff --git a/apps/api/src/modules/project/mapper/create-project-mapper.ts b/apps/api/src/modules/project/mapper/create-project-mapper.ts index a22d17477..4488ba19f 100644 --- a/apps/api/src/modules/project/mapper/create-project-mapper.ts +++ b/apps/api/src/modules/project/mapper/create-project-mapper.ts @@ -56,6 +56,7 @@ export function createProjectRequestMapper( data.editorOptions.showLineNumbers ?? EditorOptionsCreateRequestSchema.properties.showLineNumbers.default, themeId: data.editorOptions.themeId, + enableLigatures: data.editorOptions.enableLigatures, }, editors: data.editors.map(editor => ({ languageId: editor.languageId, diff --git a/apps/api/src/modules/project/mapper/get-project-by-id-mapper.ts b/apps/api/src/modules/project/mapper/get-project-by-id-mapper.ts index 25e82109e..c2c7eccd0 100644 --- a/apps/api/src/modules/project/mapper/get-project-by-id-mapper.ts +++ b/apps/api/src/modules/project/mapper/get-project-by-id-mapper.ts @@ -37,6 +37,7 @@ export function createCompleteProjectGetByIdResponseMapper( fontId: data.editorOptions.fontId, showLineNumbers: data.editorOptions.showLineNumbers, themeId: data.editorOptions.themeId, + enableLigatures: data.editorOptions.enableLigatures, }, editorTabs: data.editorTabs.map(editor => ({ projectId: data.id, diff --git a/apps/api/src/modules/project/schema/project-create.schema.ts b/apps/api/src/modules/project/schema/project-create.schema.ts index 813c70373..e2feaca72 100644 --- a/apps/api/src/modules/project/schema/project-create.schema.ts +++ b/apps/api/src/modules/project/schema/project-create.schema.ts @@ -54,6 +54,7 @@ export const EditorOptionsCreateRequestSchema = Type.Object( fontWeight: Type.Number({default: 400}), themeId: Type.String(), showLineNumbers: Nullable(Type.Boolean(), {default: false}), + enableLigatures: Type.Boolean(), }, { title: 'EditorOptionsCreateRequest', diff --git a/apps/api/src/modules/project/schema/project-update.schema.ts b/apps/api/src/modules/project/schema/project-update.schema.ts index 690b610e3..2e571020e 100644 --- a/apps/api/src/modules/project/schema/project-update.schema.ts +++ b/apps/api/src/modules/project/schema/project-update.schema.ts @@ -49,6 +49,7 @@ const EditorOptionsUpdateRequestSchema = Type.Object( fontWeight: Type.Number(), themeId: Type.String(), showLineNumbers: Type.Boolean(), + enableLigatures: Type.Boolean(), }, { title: 'EditorOptionsUpdateRequest', diff --git a/apps/api/src/modules/project/schema/project.schema.ts b/apps/api/src/modules/project/schema/project.schema.ts index 8b43d70b7..ff048895e 100644 --- a/apps/api/src/modules/project/schema/project.schema.ts +++ b/apps/api/src/modules/project/schema/project.schema.ts @@ -53,4 +53,5 @@ export const BaseSnippetEditorOptionsSchema = Type.Object({ fontWeight: Type.Number(), showLineNumbers: Type.Boolean(), themeId: Type.String(), + enableLigatures: Type.Boolean(), }); diff --git a/apps/api/test/modules/project/mapper/create-project-mapper.test.ts b/apps/api/test/modules/project/mapper/create-project-mapper.test.ts index aa645a02e..c6b653b28 100644 --- a/apps/api/test/modules/project/mapper/create-project-mapper.test.ts +++ b/apps/api/test/modules/project/mapper/create-project-mapper.test.ts @@ -32,6 +32,7 @@ t.test( showLineNumbers: null, themeId: 'themeId', fontWeight: 400, + enableLigatures: true, }, }); @@ -62,6 +63,7 @@ t.test( showLineNumbers: false, themeId: 'themeId', fontWeight: 400, + enableLigatures: true, }, } as DomainModel.ProjectCreateRequest); }, diff --git a/apps/api/test/modules/project/mapper/get-project-by-id-mapper.test.ts b/apps/api/test/modules/project/mapper/get-project-by-id-mapper.test.ts index ea631a910..c0207b58a 100644 --- a/apps/api/test/modules/project/mapper/get-project-by-id-mapper.test.ts +++ b/apps/api/test/modules/project/mapper/get-project-by-id-mapper.test.ts @@ -44,6 +44,7 @@ t.test( showLineNumbers: true, themeId: 'themeId', fontWeight: 400, + enableLigatures: true, }, editorTabs: [], }); @@ -83,6 +84,7 @@ t.test( showLineNumbers: true, themeId: 'themeId', fontWeight: 400, + enableLigatures: true, }, } as SchemaModel.ProjectCompleteResponse); }, diff --git a/apps/api/test/modules/project/service/project.service.test.ts b/apps/api/test/modules/project/service/project.service.test.ts index 4e7873b9e..ef0d63c03 100644 --- a/apps/api/test/modules/project/service/project.service.test.ts +++ b/apps/api/test/modules/project/service/project.service.test.ts @@ -85,6 +85,7 @@ const baseResponse = { fontWeight: 300, showLineNumbers: true, themeId: 'themeId', + enableLigatures: true, }, frame: { background: '#fff', @@ -124,6 +125,7 @@ t.test('create project', async t => { fontWeight: 300, showLineNumbers: true, themeId: 'themeId', + enableLigatures: true, }, editors: [], frame: { @@ -265,11 +267,13 @@ t.test('clone -> should return cloned project', async t => { createdAt: new Date(), }; - let createNewProjectStub = sinon.stub(service, 'createNewProject').resolves({ - ...baseResponse, - id: baseResponse.id, - name: 'Existing', - }); + const createNewProjectStub = sinon + .stub(service, 'createNewProject') + .resolves({ + ...baseResponse, + id: baseResponse.id, + name: 'Existing', + }); const result1 = await service.clone(user, 'projectId1', null); diff --git a/apps/api/test/routes/v1/project/create.integration.test.ts b/apps/api/test/routes/v1/project/create.integration.test.ts index fa046837c..84411d4d2 100644 --- a/apps/api/test/routes/v1/project/create.integration.test.ts +++ b/apps/api/test/routes/v1/project/create.integration.test.ts @@ -34,6 +34,7 @@ t.test('POST /v1/project/ [Create Project] -> 200', async t => { showLineNumbers: true, fontId: '1', themeId: 'default', + enableLigatures: true, }, terminal: { opacity: 1, diff --git a/apps/api/test/routes/v1/project/update.integration.test.ts b/apps/api/test/routes/v1/project/update.integration.test.ts index fa2fa2704..9f9b3dcec 100644 --- a/apps/api/test/routes/v1/project/update.integration.test.ts +++ b/apps/api/test/routes/v1/project/update.integration.test.ts @@ -48,6 +48,7 @@ t.test('POST /v1/project/:id [Update Project] -> 200', async t => { showLineNumbers: false, fontId: '3', themeId: 'vscode', + enableLigatures: true, }, terminal: { opacity: 0, @@ -95,6 +96,7 @@ t.test('POST /v1/project/:id [Update Project] -> 200', async t => { showLineNumbers: false, fontId: '3', themeId: 'vscode', + enableLigatures: true, } as ProjectUpdateResponse['editorOptions'], 'return updated editor options', ); diff --git a/apps/codeimage/src/components/CustomEditor/CustomEditor.tsx b/apps/codeimage/src/components/CustomEditor/CustomEditor.tsx index 59c0dc7af..cc64926dc 100644 --- a/apps/codeimage/src/components/CustomEditor/CustomEditor.tsx +++ b/apps/codeimage/src/components/CustomEditor/CustomEditor.tsx @@ -45,11 +45,6 @@ import { } from 'solid-js'; import {createTabIcon} from '../../hooks/use-tab-icon'; -interface CustomFontExtensionOptions { - fontName: string; - fontWeight: number; -} - const EDITOR_BASE_SETUP: Extension = [ highlightSpecialChars(), drawSelection(), @@ -152,31 +147,29 @@ export default function CustomEditor(props: VoidProps) { }, }); - const createCustomFontExtension = ( - options: CustomFontExtensionOptions, - ): Extension => { + const customFontExtension = (): Extension => { + const fontName = + fonts.find(({id}) => editorState.options.fontId === id)?.name || + fonts[0].name, + fontWeight = editorState.options.fontWeight, + enableLigatures = editorState.options.enableLigatures; + + const fontVariantLigatures = !!enableLigatures ? 'normal' : 'none'; + return EditorView.theme({ '.cm-content *': { - fontFamily: `${options.fontName}, monospace`, - fontWeight: options.fontWeight, - fontVariantLigatures: 'normal', + fontFamily: `${fontName}, monospace`, + fontWeight: fontWeight, + fontVariantLigatures, }, '.cm-gutters': { - fontFamily: `${options.fontName}, monospace`, + fontFamily: `${fontName}, monospace`, fontWeight: 400, - fontVariantLigatures: 'normal', + fontVariantLigatures, }, }); }; - const customFontExtension = () => - createCustomFontExtension({ - fontName: - fonts.find(({id}) => editorState.options.fontId === id)?.name || - fonts[0].name, - fontWeight: editorState.options.fontWeight, - }); - onMount(() => { setRef(() => editorEl); import('./fix-cm-aria-roles-lighthouse').then(m => { @@ -193,7 +186,7 @@ export default function CustomEditor(props: VoidProps) { createEditorControlledValue(editorView, () => editor()?.code ?? ''); createEditorReadonly(editorView, () => props.readOnly); createExtension(EditorView.lineWrapping); - createExtension(customFontExtension); + createExtension(() => customFontExtension()); createExtension(currentLanguage); createExtension(currentExtraLanguage); createExtension(() => diff --git a/apps/codeimage/src/components/PropertyEditor/EditorStyleForm.tsx b/apps/codeimage/src/components/PropertyEditor/EditorStyleForm.tsx index bb827397f..9455974fe 100644 --- a/apps/codeimage/src/components/PropertyEditor/EditorStyleForm.tsx +++ b/apps/codeimage/src/components/PropertyEditor/EditorStyleForm.tsx @@ -12,6 +12,7 @@ import {useModality} from '@core/hooks/isMobile'; import {SkeletonLine} from '@ui/Skeleton/Skeleton'; import {createMemo, ParentComponent, Show} from 'solid-js'; import {AppLocaleEntries} from '../../i18n'; +import {PanelDivider} from './PanelDivider'; import {PanelHeader} from './PanelHeader'; import {PanelRow, TwoColumnPanelRow} from './PanelRow'; import {SuspenseEditorItem} from './SuspenseEditorItem'; @@ -35,7 +36,7 @@ export const EditorStyleForm: ParentComponent = () => { const {editor, setLanguageId} = getActiveEditorStore(); const { state, - actions: {setShowLineNumbers, setFontWeight, setFontId}, + actions: {setShowLineNumbers, setFontWeight, setFontId, setEnableLigatures}, computed: {font}, } = getRootEditorStore(); @@ -127,6 +128,10 @@ export const EditorStyleForm: ParentComponent = () => { + + + + { + + + + } + > + + + + )} diff --git a/apps/codeimage/src/i18n/sidebar.ts b/apps/codeimage/src/i18n/sidebar.ts index aa5a1be70..7b9f2e88f 100644 --- a/apps/codeimage/src/i18n/sidebar.ts +++ b/apps/codeimage/src/i18n/sidebar.ts @@ -20,6 +20,7 @@ export default { fontWeight: 'Font weight', reflection: 'Reflection', backgroundType: 'Background', + ligatures: 'Ligatures', }, }, it: { @@ -43,6 +44,7 @@ export default { watermark: 'Watermark', backgroundType: 'Tipo background', theme: 'Tema', + ligatures: 'Ligatures', }, }, de: { @@ -66,6 +68,7 @@ export default { watermark: 'Wasserzeichen', backgroundType: 'Hintergrundtyp', theme: 'Theme', + ligatures: 'Ligatures', }, }, es: { @@ -89,6 +92,7 @@ export default { watermark: 'filigrana', backgroundType: 'Tipo de fondo', theme: 'Theme', + ligatures: 'Ligatures', }, }, } as const; diff --git a/apps/codeimage/src/state/editor/editor.ts b/apps/codeimage/src/state/editor/editor.ts index f55ca9b56..36d16f922 100644 --- a/apps/codeimage/src/state/editor/editor.ts +++ b/apps/codeimage/src/state/editor/editor.ts @@ -33,6 +33,7 @@ export function getInitialEditorUiOptions(): EditorUIOptions { fontId: appEnvironment.defaultState.editor.font.id, fontWeight: appEnvironment.defaultState.editor.font.types[0].weight, focused: false, + enableLigatures: true, }; } @@ -52,6 +53,7 @@ export function createEditorsStore() { setFontWeight: number; setShowLineNumbers: boolean; setFromPersistedState: PersistedEditorState; + setEnableLigatures: boolean; }>(), ); @@ -81,6 +83,9 @@ export function createEditorsStore() { .hold(store.commands.setShowLineNumbers, (showLineNumbers, {set}) => set('options', 'showLineNumbers', showLineNumbers), ) + .hold(store.commands.setEnableLigatures, (enable, {set}) => + set('options', 'enableLigatures', enable), + ) .hold(store.commands.setFromPersistedState, (persistedState, {state}) => { const editors = (persistedState.editors ?? []) .slice(0, MAX_TABS) @@ -128,6 +133,7 @@ export function createEditorsStore() { showLineNumbers: state.options.showLineNumbers, fontId: state.options.fontId, fontWeight: state.options.fontWeight, + enableLigatures: state.options.enableLigatures ?? true, }, }; }; @@ -138,6 +144,7 @@ export function createEditorsStore() { store.commands.setThemeId, store.commands.setFontWeight, store.commands.setShowLineNumbers, + store.commands.setEnableLigatures, editorUpdateCommand, ]) .pipe( diff --git a/apps/codeimage/src/state/editor/model.ts b/apps/codeimage/src/state/editor/model.ts index 313294b0c..1817e2312 100644 --- a/apps/codeimage/src/state/editor/model.ts +++ b/apps/codeimage/src/state/editor/model.ts @@ -26,6 +26,7 @@ export interface EditorUIOptions { showLineNumbers: boolean; focused: boolean; themeId: string; + enableLigatures: boolean; } export interface PersistedEditorState {