add-cms
This commit is contained in:
179
source/admin/packages/decap-cms-widget-code/CHANGELOG.md
Normal file
179
source/admin/packages/decap-cms-widget-code/CHANGELOG.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.2.0](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.1.4...decap-cms-widget-code@3.2.0) (2025-06-26)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [3.1.4](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.1.3...decap-cms-widget-code@3.1.4) (2024-08-13)
|
||||
|
||||
### Reverts
|
||||
|
||||
- Revert "Update dependencies (#7264)" ([22d483a](https://github.com/decaporg/decap-cms/commit/22d483a5b0c654071ae05735ac4f49abdc13d38c)), closes [#7264](https://github.com/decaporg/decap-cms/issues/7264)
|
||||
|
||||
## [3.1.3](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.1.2...decap-cms-widget-code@3.1.3) (2024-08-13)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [3.1.2](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.1.1...decap-cms-widget-code@3.1.2) (2024-03-21)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [3.1.1](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.1.0-beta.1...decap-cms-widget-code@3.1.1) (2024-03-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- /decaporg/decap-cms/issues/6812 show code widget content if initially hidden ([#7131](https://github.com/decaporg/decap-cms/issues/7131)) ([b18b51b](https://github.com/decaporg/decap-cms/commit/b18b51bc876d3aad887b56ea1ef191c45b40fbcb))
|
||||
|
||||
# [3.1.0](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.1.0-beta.1...decap-cms-widget-code@3.1.0) (2024-02-01)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
# [3.1.0-beta.1](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.1.0-beta.0...decap-cms-widget-code@3.1.0-beta.1) (2024-01-31)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
# [3.1.0-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.1.0...decap-cms-widget-code@3.1.0-beta.0) (2023-10-20)
|
||||
|
||||
### Reverts
|
||||
|
||||
- Revert "chore(release): publish" ([b89fc89](https://github.com/decaporg/decap-cms/commit/b89fc894dfbb5f4136b2e5427fd25a29378a58c6))
|
||||
|
||||
## [3.0.2](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.0.1...decap-cms-widget-code@3.0.2) (2023-10-13)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [3.0.1](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@3.0.0...decap-cms-widget-code@3.0.1) (2023-08-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- update peer dependencies ([#6886](https://github.com/decaporg/decap-cms/issues/6886)) ([e580ce5](https://github.com/decaporg/decap-cms/commit/e580ce52ce5f80fa040e8fbcab7fed0744f4f695))
|
||||
|
||||
# [3.0.0](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@1.4.0...decap-cms-widget-code@3.0.0) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
# [1.4.0](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@1.4.0-beta.0...decap-cms-widget-code@1.4.0) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
# 1.4.0-beta.0 (2023-08-18)
|
||||
|
||||
### Features
|
||||
|
||||
- rename packages ([#6863](https://github.com/decaporg/decap-cms/issues/6863)) ([d515e7b](https://github.com/decaporg/decap-cms/commit/d515e7bd33216a775d96887b08c4f7b1962941bb))
|
||||
|
||||
## [1.3.5-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@1.3.4...decap-cms-widget-code@1.3.5-beta.0) (2023-07-27)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [1.3.4](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-code@1.3.3...decap-cms-widget-code@1.3.4) (2022-03-08)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [1.3.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.3.2...decap-cms-widget-code@1.3.3) (2021-06-01)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [1.3.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.3.1...decap-cms-widget-code@1.3.2) (2021-05-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **deps:** update dependency react-select to v4 ([#5417](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/5417)) ([03362ef](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/03362ef5ab87c6fe5c964da5c5a18099b73a3fc6))
|
||||
|
||||
## [1.3.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.3.0...decap-cms-widget-code@1.3.1) (2021-05-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **deps:** update react-select to v3 ([#5394](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/5394)) ([03be13c](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/03be13c1e87b318fd10ae6f6ab54cd2634fb9662))
|
||||
|
||||
# [1.3.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.2.8...decap-cms-widget-code@1.3.0) (2021-05-04)
|
||||
|
||||
### Features
|
||||
|
||||
- added react 17 as peer dependency in packages ([#5316](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/5316)) ([9e42380](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/9e423805707321396eec137f5b732a5b07a0dd3f))
|
||||
|
||||
## [1.2.8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.2.7...decap-cms-widget-code@1.2.8) (2021-03-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add !important to codeMirror height ([#5127](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/5127)) ([50ab350](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/50ab3504e533353bfefc65480edf8a53bb497acf))
|
||||
|
||||
## [1.2.7](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.2.6...decap-cms-widget-code@1.2.7) (2021-02-23)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [1.2.6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.2.5...decap-cms-widget-code@1.2.6) (2021-02-10)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [1.2.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.2.4...decap-cms-widget-code@1.2.5) (2021-01-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **deps:** update dependency js-yaml to v4 ([#4797](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/4797)) ([bda604b](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/bda604b389071ab2dd31a7107841aa7fcafdc04f))
|
||||
|
||||
## [1.2.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.2.3...decap-cms-widget-code@1.2.4) (2020-09-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **deps:** update dependency re-resizable to v6 ([#4308](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/4308)) ([de068cb](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/de068cba1d44ec76e47e28d724427a9f4a53e0fd))
|
||||
- **deps:** update dependency react-codemirror2 to v7 ([#4310](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/4310)) ([180cd5f](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/180cd5f652ba23a186ade9173161ac13605a8ce8))
|
||||
|
||||
## 1.2.3 (2020-09-08)
|
||||
|
||||
### Reverts
|
||||
|
||||
- Revert "chore(release): publish" ([828bb16](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/828bb16415b8c22a34caa19c50c38b24ffe9ceae))
|
||||
|
||||
## 1.2.2 (2020-08-20)
|
||||
|
||||
### Reverts
|
||||
|
||||
- Revert "chore(release): publish" ([8262487](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/82624879ccbcb16610090041db28f00714d924c8))
|
||||
|
||||
## 1.2.1 (2020-07-27)
|
||||
|
||||
### Reverts
|
||||
|
||||
- Revert "chore(release): publish" ([118d50a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/118d50a7a70295f25073e564b5161aa2b9883056))
|
||||
|
||||
# [1.2.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.1.5...decap-cms-widget-code@1.2.0) (2020-06-18)
|
||||
|
||||
### Features
|
||||
|
||||
- add widgets schema validation ([#3841](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/3841)) ([2b46608](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/2b46608f86d22c8ad34f75e396be7c34462d9e99))
|
||||
|
||||
## [1.1.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.1.4...decap-cms-widget-code@1.1.5) (2020-05-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **widget-code:** change dynamic import to require ([#3745](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/3745)) ([3d7d5d2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/3d7d5d2e677fa0bb2bd6e2a65df302053ba4d159))
|
||||
|
||||
## [1.1.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.1.3...decap-cms-widget-code@1.1.4) (2020-03-30)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [1.1.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.1.2...decap-cms-widget-code@1.1.3) (2020-01-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **widget-code:** use snake case for default language option ([#3155](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/3155)) ([32854de](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/32854de41c1d0ef81836ffdad8851a583086d6a6))
|
||||
|
||||
## [1.1.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.1.2-beta.0...decap-cms-widget-code@1.1.2) (2020-01-07)
|
||||
|
||||
**Note:** Version bump only for package decap-cms-widget-code
|
||||
|
||||
## [1.1.2-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/compare/decap-cms-widget-code@1.1.0...decap-cms-widget-code@1.1.2-beta.0) (2019-12-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- allow widget overflow ([#2982](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/2982)) ([5ea2b6f](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/5ea2b6fe2f3ccb5e465f65fca359baf7210e5fdb))
|
||||
|
||||
# 1.1.0 (2019-12-16)
|
||||
|
||||
### Features
|
||||
|
||||
- Code Widget + Markdown Widget Internal Overhaul ([#2828](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/issues/2828)) ([18c579d](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code/commit/18c579d0e9f0ff71ed8c52f5c66f2309259af054))
|
||||
9
source/admin/packages/decap-cms-widget-code/README.md
Normal file
9
source/admin/packages/decap-cms-widget-code/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Docs coming soon!
|
||||
|
||||
Decap CMS was converted from a single npm package to a "monorepo" of over 20 packages.
|
||||
We haven't created a README for this package yet, but you can:
|
||||
|
||||
1. Check out the [main readme](https://github.com/decaporg/decap-cms/#readme) or the [documentation
|
||||
site](https://www.decapcms.org) for more info.
|
||||
2. Reach out to the [community chat](https://decapcms.org/chat) if you need help.
|
||||
3. Help out and [write the readme yourself](https://github.com/decaporg/decap-cms/edit/main/packages/decap-cms-widget-code/README.md)!
|
||||
6133
source/admin/packages/decap-cms-widget-code/data/languages-raw.yml
Normal file
6133
source/admin/packages/decap-cms-widget-code/data/languages-raw.yml
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
37
source/admin/packages/decap-cms-widget-code/package.json
Normal file
37
source/admin/packages/decap-cms-widget-code/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "decap-cms-widget-code",
|
||||
"description": "Widget for editing code in Decap CMS",
|
||||
"version": "3.2.0",
|
||||
"homepage": "https://www.decapcms.org/docs/widgets/#code",
|
||||
"repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-code",
|
||||
"bugs": "https://github.com/decaporg/decap-cms/issues",
|
||||
"module": "dist/esm/index.js",
|
||||
"main": "dist/decap-cms-widget-code.js",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"decap-cms",
|
||||
"widget",
|
||||
"code",
|
||||
"codemirror",
|
||||
"editor",
|
||||
"code editor"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"develop": "npm run build:esm -- --watch",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward",
|
||||
"process:languages": "node ./scripts/process-languages"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"codemirror": "^5.46.0",
|
||||
"decap-cms-ui-default": "^3.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"react": "^19.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-codemirror2": "^7.0.0",
|
||||
"react-select": "^5.10.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
const uniq = require('lodash/uniq');
|
||||
|
||||
const rawDataPath = '../data/languages-raw.yml';
|
||||
const outputPath = '../data/languages.json';
|
||||
|
||||
async function fetchData() {
|
||||
const filePath = path.resolve(__dirname, rawDataPath);
|
||||
const fileContent = await fs.readFile(filePath);
|
||||
return yaml.load(fileContent);
|
||||
}
|
||||
|
||||
function outputData(data) {
|
||||
const filePath = path.resolve(__dirname, outputPath);
|
||||
return fs.writeJson(filePath, data);
|
||||
}
|
||||
|
||||
function transform(data) {
|
||||
return Object.entries(data).reduce((acc, [label, lang]) => {
|
||||
const { extensions = [], aliases = [], codemirror_mode, codemirror_mime_type } = lang;
|
||||
if (codemirror_mode) {
|
||||
const dotlessExtensions = extensions.map(ext => ext.slice(1));
|
||||
const identifiers = uniq(
|
||||
[label.toLowerCase(), ...aliases, ...dotlessExtensions].filter(alias => {
|
||||
if (!alias) {
|
||||
return;
|
||||
}
|
||||
return !/[^a-zA-Z]/.test(alias);
|
||||
}),
|
||||
);
|
||||
acc.push({ label, identifiers, codemirror_mode, codemirror_mime_type });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
async function process() {
|
||||
const data = await fetchData();
|
||||
const transformedData = transform(data);
|
||||
return outputData(transformedData);
|
||||
}
|
||||
|
||||
process();
|
||||
346
source/admin/packages/decap-cms-widget-code/src/CodeControl.js
Normal file
346
source/admin/packages/decap-cms-widget-code/src/CodeControl.js
Normal file
@@ -0,0 +1,346 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { ClassNames } from '@emotion/react';
|
||||
import { Map } from 'immutable';
|
||||
import uniq from 'lodash/uniq';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { UnControlled as ReactCodeMirror } from 'react-codemirror2';
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/keymap/vim';
|
||||
import 'codemirror/keymap/sublime';
|
||||
import 'codemirror/keymap/emacs';
|
||||
import codeMirrorStyles from 'codemirror/lib/codemirror.css';
|
||||
import materialTheme from 'codemirror/theme/material.css';
|
||||
|
||||
import SettingsPane from './SettingsPane';
|
||||
import SettingsButton from './SettingsButton';
|
||||
import languageData from '../data/languages.json';
|
||||
|
||||
// TODO: relocate as a utility function
|
||||
function getChangedProps(previous, next, keys) {
|
||||
const propNames = keys || uniq(Object.keys(previous), Object.keys(next));
|
||||
const changedProps = propNames.reduce((acc, prop) => {
|
||||
if (previous[prop] !== next[prop]) {
|
||||
acc[prop] = next[prop];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
if (!isEmpty(changedProps)) {
|
||||
return changedProps;
|
||||
}
|
||||
}
|
||||
|
||||
const languages = languageData.map(lang => ({
|
||||
label: lang.label,
|
||||
name: lang.identifiers[0],
|
||||
mode: lang.codemirror_mode,
|
||||
mimeType: lang.codemirror_mime_type,
|
||||
}));
|
||||
|
||||
const styleString = `
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
const defaultLang = { name: '', mode: '', label: 'none' };
|
||||
|
||||
function valueToOption(val) {
|
||||
if (typeof val === 'string') {
|
||||
return { value: val, label: val };
|
||||
}
|
||||
return { value: val.name, label: val.label || val.name };
|
||||
}
|
||||
|
||||
const modes = languages.map(valueToOption);
|
||||
|
||||
const themes = ['default', 'material'];
|
||||
|
||||
const settingsPersistKeys = {
|
||||
theme: 'cms.codemirror.theme',
|
||||
keyMap: 'cms.codemirror.keymap',
|
||||
};
|
||||
|
||||
export default class CodeControl extends React.Component {
|
||||
static propTypes = {
|
||||
field: ImmutablePropTypes.map.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.node,
|
||||
forID: PropTypes.string.isRequired,
|
||||
classNameWrapper: PropTypes.string.isRequired,
|
||||
widget: PropTypes.object.isRequired,
|
||||
isParentListCollapsed: PropTypes.bool,
|
||||
};
|
||||
|
||||
keys = this.getKeys(this.props.field);
|
||||
|
||||
state = {
|
||||
isActive: false,
|
||||
unknownLang: null,
|
||||
lang: '',
|
||||
keyMap: localStorage.getItem(settingsPersistKeys['keyMap']) || 'default',
|
||||
settingsVisible: false,
|
||||
codeMirrorKey: uuid(),
|
||||
theme: localStorage.getItem(settingsPersistKeys['theme']) || themes[themes.length - 1],
|
||||
lastKnownValue: this.valueIsMap() ? this.props.value?.get(this.keys.code) : this.props.value,
|
||||
};
|
||||
|
||||
visibility = {
|
||||
isInvisibleOnInit: this.props.isParentListCollapsed === true,
|
||||
isRefreshedAfterInvisible: false,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return (
|
||||
!isEqual(this.state, nextState) ||
|
||||
this.props.classNameWrapper !== nextProps.classNameWrapper ||
|
||||
(this.visibility.isInvisibleOnInit && !this.visibility.isRefreshedAfterInvisible)
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Manually validate PropTypes - React 19 breaking change
|
||||
PropTypes.checkPropTypes(CodeControl.propTypes, this.props, 'prop', 'CodeControl');
|
||||
|
||||
this.setState({
|
||||
lang: this.getInitialLang() || '',
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
this.updateCodeMirrorProps(prevState);
|
||||
// when initially hidden and then shown, codeMirror content is not visible
|
||||
if (
|
||||
this.visibility.isInvisibleOnInit &&
|
||||
!this.visibility.isRefreshedAfterInvisible &&
|
||||
!this.props.isParentListCollapsed
|
||||
) {
|
||||
this.refreshCodeMirrorInstance();
|
||||
}
|
||||
}
|
||||
|
||||
updateCodeMirrorProps(prevState) {
|
||||
const keys = ['lang', 'theme', 'keyMap'];
|
||||
const changedProps = getChangedProps(prevState, this.state, keys);
|
||||
if (changedProps) {
|
||||
this.handleChangeCodeMirrorProps(changedProps);
|
||||
}
|
||||
}
|
||||
|
||||
refreshCodeMirrorInstance() {
|
||||
if (this.cm?.getWrapperElement().offsetHeight) {
|
||||
this.cm.refresh();
|
||||
this.visibility.isRefreshedAfterInvisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
getLanguageByName = name => {
|
||||
return languages.find(lang => lang.name === name);
|
||||
};
|
||||
|
||||
getKeyMapOptions = () => {
|
||||
return Object.keys(CodeMirror.keyMap)
|
||||
.sort()
|
||||
.filter(keyMap => ['emacs', 'vim', 'sublime', 'default'].includes(keyMap))
|
||||
.map(keyMap => ({ value: keyMap, label: keyMap }));
|
||||
};
|
||||
|
||||
// This widget is not fully controlled, it only takes a value through props
|
||||
// upon initialization.
|
||||
getInitialLang = () => {
|
||||
const { value, field } = this.props;
|
||||
const lang =
|
||||
(this.valueIsMap() && value && value.get(this.keys.lang)) || field.get('default_language');
|
||||
const langInfo = this.getLanguageByName(lang);
|
||||
if (lang && !langInfo) {
|
||||
this.setState({ unknownLang: lang });
|
||||
}
|
||||
return lang;
|
||||
};
|
||||
|
||||
// If `allow_language_selection` is not set, default to true. Otherwise, use
|
||||
// its value.
|
||||
allowLanguageSelection =
|
||||
!this.props.field.has('allow_language_selection') ||
|
||||
!!this.props.field.get('allow_language_selection');
|
||||
|
||||
toValue = this.valueIsMap()
|
||||
? (type, value) => (this.props.value || Map()).set(this.keys[type], value)
|
||||
: (type, value) => (type === 'code' ? value : this.props.value);
|
||||
|
||||
// If the value is a map, keys can be customized via config.
|
||||
getKeys(field) {
|
||||
const defaults = {
|
||||
code: 'code',
|
||||
lang: 'lang',
|
||||
};
|
||||
|
||||
// Force default keys if widget is an editor component code block.
|
||||
if (this.props.isEditorComponent) {
|
||||
return defaults;
|
||||
}
|
||||
|
||||
const keys = field.get('keys', Map()).toJS();
|
||||
return { ...defaults, ...keys };
|
||||
}
|
||||
|
||||
// Determine if the persisted value is a map rather than a plain string. A map
|
||||
// value allows both the code string and the language to be persisted.
|
||||
valueIsMap() {
|
||||
const { field, isEditorComponent } = this.props;
|
||||
return !field.get('output_code_only') || isEditorComponent;
|
||||
}
|
||||
|
||||
async handleChangeCodeMirrorProps(changedProps) {
|
||||
const { onChange } = this.props;
|
||||
|
||||
if (changedProps.lang) {
|
||||
const { mode } = this.getLanguageByName(changedProps.lang) || {};
|
||||
if (mode) {
|
||||
require(`codemirror/mode/${mode}/${mode}.js`);
|
||||
}
|
||||
}
|
||||
|
||||
// Changing CodeMirror props requires re-initializing the
|
||||
// detached/uncontrolled React CodeMirror component, so here we save and
|
||||
// restore the selections and cursor position after the state change.
|
||||
if (this.cm) {
|
||||
const cursor = this.cm.doc.getCursor();
|
||||
const selections = this.cm.doc.listSelections();
|
||||
this.setState({ codeMirrorKey: uuid() }, () => {
|
||||
this.cm.doc.setCursor(cursor);
|
||||
this.cm.doc.setSelections(selections);
|
||||
});
|
||||
}
|
||||
|
||||
for (const key of ['theme', 'keyMap']) {
|
||||
if (changedProps[key]) {
|
||||
localStorage.setItem(settingsPersistKeys[key], changedProps[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Only persist the language change if supported - requires the value to be
|
||||
// a map rather than just a code string.
|
||||
if (changedProps.lang && this.valueIsMap()) {
|
||||
onChange(this.toValue('lang', changedProps.lang));
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(newValue) {
|
||||
const cursor = this.cm.doc.getCursor();
|
||||
const selections = this.cm.doc.listSelections();
|
||||
this.setState({ lastKnownValue: newValue });
|
||||
this.props.onChange(this.toValue('code', newValue), { cursor, selections });
|
||||
}
|
||||
|
||||
showSettings = () => {
|
||||
this.setState({ settingsVisible: true });
|
||||
};
|
||||
|
||||
hideSettings = () => {
|
||||
if (this.state.settingsVisible) {
|
||||
this.setState({ settingsVisible: false });
|
||||
}
|
||||
this.cm.focus();
|
||||
};
|
||||
|
||||
handleFocus = () => {
|
||||
this.hideSettings();
|
||||
this.props.setActiveStyle();
|
||||
this.setActive();
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
this.setInactive();
|
||||
this.props.setInactiveStyle();
|
||||
};
|
||||
|
||||
setActive = () => this.setState({ isActive: true });
|
||||
setInactive = () => this.setState({ isActive: false });
|
||||
|
||||
render() {
|
||||
const { classNameWrapper, forID, widget, isNewEditorComponent } = this.props;
|
||||
const { lang, settingsVisible, keyMap, codeMirrorKey, theme, lastKnownValue } = this.state;
|
||||
const langInfo = this.getLanguageByName(lang);
|
||||
const mode = langInfo?.mimeType || langInfo?.mode;
|
||||
|
||||
return (
|
||||
<ClassNames>
|
||||
{({ css, cx }) => (
|
||||
<div
|
||||
className={cx(
|
||||
classNameWrapper,
|
||||
css`
|
||||
${codeMirrorStyles};
|
||||
${materialTheme};
|
||||
${styleString};
|
||||
`,
|
||||
)}
|
||||
>
|
||||
{!settingsVisible && <SettingsButton onClick={this.showSettings} />}
|
||||
{settingsVisible && (
|
||||
<SettingsPane
|
||||
hideSettings={this.hideSettings}
|
||||
forID={forID}
|
||||
modes={modes}
|
||||
mode={valueToOption(langInfo || defaultLang)}
|
||||
theme={themes.find(t => t === theme)}
|
||||
themes={themes}
|
||||
keyMap={{ value: keyMap, label: keyMap }}
|
||||
keyMaps={this.getKeyMapOptions()}
|
||||
allowLanguageSelection={this.allowLanguageSelection}
|
||||
onChangeLang={newLang => this.setState({ lang: newLang })}
|
||||
onChangeTheme={newTheme => this.setState({ theme: newTheme })}
|
||||
onChangeKeyMap={newKeyMap => this.setState({ keyMap: newKeyMap })}
|
||||
/>
|
||||
)}
|
||||
<ReactCodeMirror
|
||||
key={codeMirrorKey}
|
||||
id={forID}
|
||||
className={css`
|
||||
height: 100%;
|
||||
border-radius: 0 3px 3px;
|
||||
overflow: hidden;
|
||||
|
||||
.CodeMirror {
|
||||
height: auto !important;
|
||||
cursor: text;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
min-height: 300px;
|
||||
}
|
||||
`}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
...widget.codeMirrorConfig,
|
||||
extraKeys: {
|
||||
'Shift-Tab': 'indentLess',
|
||||
Tab: 'indentMore',
|
||||
...(widget.codeMirrorConfig.extraKeys || {}),
|
||||
},
|
||||
theme,
|
||||
mode,
|
||||
keyMap,
|
||||
viewportMargin: Infinity,
|
||||
}}
|
||||
detach={true}
|
||||
editorDidMount={cm => {
|
||||
this.cm = cm;
|
||||
if (isNewEditorComponent) {
|
||||
this.handleFocus();
|
||||
}
|
||||
}}
|
||||
value={lastKnownValue}
|
||||
onChange={(editor, data, newValue) => this.handleChange(newValue)}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ClassNames>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Map } from 'immutable';
|
||||
import isString from 'lodash/isString';
|
||||
import { WidgetPreviewContainer } from 'decap-cms-ui-default';
|
||||
|
||||
function toValue(value, field) {
|
||||
if (isString(value)) {
|
||||
return value;
|
||||
}
|
||||
if (Map.isMap(value)) {
|
||||
return value.get(field.getIn(['keys', 'code'], 'code'), '');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function CodePreview(props) {
|
||||
return (
|
||||
<WidgetPreviewContainer>
|
||||
<pre>
|
||||
<code>{toValue(props.value, props.field)}</code>
|
||||
</pre>
|
||||
</WidgetPreviewContainer>
|
||||
);
|
||||
}
|
||||
|
||||
CodePreview.propTypes = {
|
||||
value: PropTypes.node,
|
||||
};
|
||||
|
||||
export default CodePreview;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Icon, buttons, shadows, zIndex } from 'decap-cms-ui-default';
|
||||
|
||||
const StyledSettingsButton = styled.button`
|
||||
${buttons.button};
|
||||
${buttons.default};
|
||||
${shadows.drop};
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: ${zIndex.zIndex100};
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
opacity: 0.8;
|
||||
padding: 2px 4px;
|
||||
line-height: 1;
|
||||
height: auto;
|
||||
|
||||
${Icon} {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
`;
|
||||
|
||||
function SettingsButton({ showClose, onClick }) {
|
||||
return (
|
||||
<StyledSettingsButton onClick={onClick}>
|
||||
<Icon type={showClose ? 'close' : 'settings'} size="small" />
|
||||
</StyledSettingsButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsButton;
|
||||
116
source/admin/packages/decap-cms-widget-code/src/SettingsPane.js
Normal file
116
source/admin/packages/decap-cms-widget-code/src/SettingsPane.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import Select from 'react-select';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import { text, shadows, zIndex } from 'decap-cms-ui-default';
|
||||
|
||||
import SettingsButton from './SettingsButton';
|
||||
import languageSelectStyles from './languageSelectStyles';
|
||||
|
||||
const SettingsPaneContainer = styled.div`
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 200px;
|
||||
z-index: ${zIndex.zIndex10};
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
padding: 12px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
${shadows.drop};
|
||||
`;
|
||||
|
||||
const SettingsFieldLabel = styled.label`
|
||||
${text.fieldLabel};
|
||||
font-size: 11px;
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 2px;
|
||||
`;
|
||||
|
||||
const SettingsSectionTitle = styled.h3`
|
||||
font-size: 14px;
|
||||
margin-top: 14px;
|
||||
margin-bottom: 0;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
function SettingsSelect({ value, options, onChange, forID, type, autoFocus }) {
|
||||
return (
|
||||
<Select
|
||||
inputId={`${forID}-select-${type}`}
|
||||
styles={languageSelectStyles}
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={opt => onChange(opt.value)}
|
||||
menuPlacement="auto"
|
||||
captureMenuScroll={false}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsPane({
|
||||
hideSettings,
|
||||
forID,
|
||||
modes,
|
||||
mode,
|
||||
theme,
|
||||
themes,
|
||||
keyMap,
|
||||
keyMaps,
|
||||
allowLanguageSelection,
|
||||
onChangeLang,
|
||||
onChangeTheme,
|
||||
onChangeKeyMap,
|
||||
}) {
|
||||
return (
|
||||
<SettingsPaneContainer onKeyDown={e => isHotkey('esc', e) && hideSettings()}>
|
||||
<SettingsButton onClick={hideSettings} showClose={true} />
|
||||
{allowLanguageSelection && (
|
||||
<>
|
||||
<SettingsSectionTitle>Field Settings</SettingsSectionTitle>
|
||||
<SettingsFieldLabel htmlFor={`${forID}-select-mode`}>Mode</SettingsFieldLabel>
|
||||
<SettingsSelect
|
||||
type="mode"
|
||||
forID={forID}
|
||||
value={mode}
|
||||
options={modes}
|
||||
onChange={onChangeLang}
|
||||
autoFocus
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<SettingsSectionTitle>Global Settings</SettingsSectionTitle>
|
||||
{themes && (
|
||||
<>
|
||||
<SettingsFieldLabel htmlFor={`${forID}-select-theme`}>Theme</SettingsFieldLabel>
|
||||
<SettingsSelect
|
||||
type="theme"
|
||||
forID={forID}
|
||||
value={{ value: theme, label: theme }}
|
||||
options={themes.map(t => ({ value: t, label: t }))}
|
||||
onChange={onChangeTheme}
|
||||
autoFocus={!allowLanguageSelection}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<SettingsFieldLabel htmlFor={`${forID}-select-keymap`}>KeyMap</SettingsFieldLabel>
|
||||
<SettingsSelect
|
||||
type="keymap"
|
||||
forID={forID}
|
||||
value={keyMap}
|
||||
options={keyMaps}
|
||||
onChange={onChangeKeyMap}
|
||||
/>
|
||||
</>
|
||||
</SettingsPaneContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsPane;
|
||||
18
source/admin/packages/decap-cms-widget-code/src/index.js
Normal file
18
source/admin/packages/decap-cms-widget-code/src/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import controlComponent from './CodeControl';
|
||||
import previewComponent from './CodePreview';
|
||||
import schema from './schema';
|
||||
|
||||
function Widget(opts = {}) {
|
||||
return {
|
||||
name: 'code',
|
||||
controlComponent,
|
||||
previewComponent,
|
||||
schema,
|
||||
allowMapValue: true,
|
||||
codeMirrorConfig: {},
|
||||
...opts,
|
||||
};
|
||||
}
|
||||
|
||||
export const DecapCmsWidgetCode = { Widget, controlComponent, previewComponent };
|
||||
export default DecapCmsWidgetCode;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { reactSelectStyles, borders } from 'decap-cms-ui-default';
|
||||
|
||||
const languageSelectStyles = {
|
||||
...reactSelectStyles,
|
||||
container: provided => ({
|
||||
...reactSelectStyles.container(provided),
|
||||
'margin-top': '2px',
|
||||
}),
|
||||
control: provided => ({
|
||||
...reactSelectStyles.control(provided),
|
||||
border: borders.textField,
|
||||
padding: 0,
|
||||
fontSize: '13px',
|
||||
minHeight: 'auto',
|
||||
}),
|
||||
dropdownIndicator: provided => ({
|
||||
...reactSelectStyles.dropdownIndicator(provided),
|
||||
padding: '4px',
|
||||
}),
|
||||
option: (provided, state) => ({
|
||||
...reactSelectStyles.option(provided, state),
|
||||
padding: 0,
|
||||
paddingLeft: '8px',
|
||||
}),
|
||||
menu: provided => ({
|
||||
...reactSelectStyles.menu(provided),
|
||||
margin: '2px 0',
|
||||
}),
|
||||
menuList: provided => ({
|
||||
...provided,
|
||||
'max-height': '200px',
|
||||
}),
|
||||
};
|
||||
|
||||
export default languageSelectStyles;
|
||||
11
source/admin/packages/decap-cms-widget-code/src/schema.js
Normal file
11
source/admin/packages/decap-cms-widget-code/src/schema.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export default {
|
||||
properties: {
|
||||
default_language: { type: 'string' },
|
||||
allow_language_selection: { type: 'boolean' },
|
||||
output_code_only: { type: 'boolean' },
|
||||
keys: {
|
||||
type: 'object',
|
||||
properties: { code: { type: 'string' }, lang: { type: 'string' } },
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
const { getConfig } = require('../../scripts/webpack.js');
|
||||
|
||||
module.exports = getConfig();
|
||||
Reference in New Issue
Block a user