Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id=Root />
              
            
!

CSS

              
                $wiki-grey: #eaecf0;
$border: 1px solid darken($wiki-grey, 15%);
$wiki-logo: url("https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/300px-Wikipedia-logo-v2.svg.png");

div,
section {
  box-sizing: border-box;
}

#App {
  font-family: 'Helvetica Neue','Helvetica','Nimbus Sans L','Arial','Liberation Sans',sans-serif;
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.SearchPanel {
  padding: .6rem;
  z-index: 1;
  box-shadow: 0 0 4px 2px rgba(0,0,0,.3);
  font-size: 1.2rem;
  display: flex;
  input {
    flex: 1;
  }
  select,
  button {
    margin-left: .5em;
    &:enabled {
      cursor: pointer;
      &:hover {
        background-color: #eee;
      }
    }
  }
  input,
  select,
  button {
    border: $border;
    padding: .2rem .5rem;
    &:focus {
      border-color: #555;
      outline: none;
    }
  }
}

#mainPanel {
  background-repeat: no-repeat;
  background-position: center;
  background-color: $wiki-grey;
  background-image: $wiki-logo;
  display: flex;
  flex: 1;
  flex-direction: row;
  .ResultList {
    background: $wiki-grey;
    flex: 1;
    width: 100%;
    height: 100%;
    overflow-y: scroll;
    
    .Result {
      padding: 0.5rem;
      border-bottom: $border;  
      cursor: pointer;
      &:hover,
      &:focus {
        background: lighten($wiki-grey, 5%);
        outline: none;
      }
      .title {
        font-weight: bold;
        font-size: .9em;
      }
      .text {
        font-size: .8em;
      }
    }
  }
  .Preview {
    border: 0;
    border-left: $border;
    flex: 2;
    width: 100%;
    background: $wiki-grey;
  }
}
              
            
!

JS

              
                const { connect, Provider } = ReactRedux
const { debounce } = _ // Lodash
const DEBOUNCE_TIMEOUT = 250 // wait n ms before calling api

// Actions

const SEARCH_CHANGED = "SEARCH_CHANGED"
const searchChanged = text => ({ type: SEARCH_CHANGED, payload: text })

const LANGUAGE_CHANGED = "LANGUAGE_CHANGED"
const languageChanged = language => ({ type: LANGUAGE_CHANGED, payload: language })

const RESULTS_FETCHED = "RESULTS_FETCHED"
const resultsFetched = data => ({ type: RESULTS_FETCHED, payload: data })

const SELECT_ARTICLE = "SELECT_ARTICLE"
const selectArticle = url => ({ type: SELECT_ARTICLE, payload: url })

// Thunk Actions

const searchAction = text => (dispatch, getState) => {
  // update search in store and queue api call to wikipedia
  dispatch(searchChanged(text))
  const language = getState().search.language
  // debounced api call
  debouncedWikiSearch(text, language, data => dispatch(resultsFetched(data)))
}

// Utility functions for wikipedia api

const performWikiSearch = (text, language, callback) => {
  // perform http call to wikipedia opensearch api
  if (!text) return // abort if search is empty
  const url = buildUrl(text, language, "json")
  console.log(url)
  fetch(url, { mode: "cors" }) // use cors mode for cross origin sharing
    .then(response => response.json())
    .then(normalizeJsonData)
    .then(callback)
    .catch(console.error)
}
const debouncedWikiSearch = debounce(
  performWikiSearch, DEBOUNCE_TIMEOUT, {maxWait: DEBOUNCE_TIMEOUT * 2}
)
// https://lodash.com/docs/4.17.4#debounce

const normalizeJsonData = ([q, titles, texts, urls]) =>
  // convert results from wikipedia opensearch api into array of objects
  titles.map((title, index) => 
    ({ index, title, text: texts[index], url: urls[index] }))

const escapeQuery = query => encodeURIComponent(query).replace(/(%20)+/g, "+")

const buildUrl = (search, language = "en", format = "json", limit = 20) => {
  // builds wikipedia api url to fetch search results
  const attrs = {
    search: escapeQuery(search), // search query
    limit, // max number of results
    format, // json or xml
    action: "opensearch", // api action
    origin: "*" // needed for Cross-Origin resource sharing
  }
  const queryString = Object.entries(attrs)
    .map(([key, value]) => `${key}=${value}`)
    .join("&")
  return `https://${language}.wikipedia.org/w/api.php?${queryString}`
}

// Components

let SearchPanel = ({ text, language, searchChanged, languageChanged }) => {
  // search input, language select and clear button
  const clearButtonOnClick = e => {
    searchChanged("")
    searchInput.focus()
  }
  const searchInputOnChange = e => {
    searchChanged(e.target.value)
  }
  const languageSelectOnChange = e => {
    languageChanged(e.target.value)
    searchChanged(text) // force new fetch from api
  }
  const languages = [
    ["en", "English"],
    ["no", "Norwegian"],
    ["es", "Spanish"],
    ["de", "German"],
    ["fr", "French"]
  ]
  let searchInput = null // ref placeholder
  return (
    <section className="SearchPanel">
      <input
        value={text}
        onChange={searchInputOnChange}
        ref={input => (searchInput = input)}
        type="search"
        placeholder="Search Wikipedia"
        spellCheck={false}
        tabIndex={1}
      />
      <select onChange={languageSelectOnChange}>
        {languages.map(([val, text]) => (
          <option value={val} selected={val === language}>{text}</option>
        ))}
      </select>
      <button disabled={!text} onClick={clearButtonOnClick}>
        Clear
      </button>
    </section>
  )
}
SearchPanel = connect(
  store => store.search,
  dispatch => ({
    searchChanged: text => dispatch(searchAction(text)),
    languageChanged: value => dispatch(languageChanged(value))
  })
)(SearchPanel)

let ResultList = ({ results }) =>
  (results.length == 0
    ? null
    : // List of search results
      <div
        className="ResultList"
        ref={div => div && (div.scrollTop = 0)} // scroll to top on render
      >
        {results.map(props => <Result {...props} />)}
      </div>)
ResultList = connect(({ results }) => ({ results }))(ResultList)

let Result = ({
  index,
  title,
  text,
  onClick,
  onKeyDown
}) => // Single search result
(
  <div
    className="Result"
    tabIndex={index + 2}
    onKeyDown={onKeyDown}
    onClick={onClick}
  >
    <div className="title">{title}</div>
    <div className="text">{text}</div>
  </div>
)
Result = connect(null, (dispatch, { url }) => ({
  onKeyDown: e => e.keyCode == 13 && dispatch(selectArticle(url)),
  onClick: e => dispatch(selectArticle(url))
}))(Result)

let Preview = ({ url }) => {
  // Iframe containing wiki article served from wikipedia
  if (!url) return null
  // use mobile url if window is narrow
  const src = window.innerWidth > 1200
    ? url
    : url.replace(/wikipedia/, "m.wikipedia")
  return <iframe className="Preview" src={src} />
}
Preview = connect(({ url }) => ({ url }))(Preview)

const App = () => // Main container
(
  <section id="App">
    <SearchPanel />
    <section id="mainPanel">
      <ResultList />
      <Preview />
    </section>
  </section>
)

// Reducers

const searchReducer = (state = { text: "", language: "en" }, action) => {
  switch (action.type) {
    case SEARCH_CHANGED:
      return { ...state, text: action.payload }
    case LANGUAGE_CHANGED:
      return { ...state, language: action.payload }
    default:
      return state
  }
}
const resultsReducer = (state = [], action) => {
  switch (action.type) {
    case RESULTS_FETCHED:
      return action.payload
    case SEARCH_CHANGED:
      // clear results if search is empty
      return action.payload ? state : []
    default:
      return state
  }
}
const urlReducer = (state = "", action) => {
  switch (action.type) {
    case SELECT_ARTICLE:
      return action.payload
    case SEARCH_CHANGED:
      // clear iframe if search is empty
      return action.payload ? state : ""
    default:
      return state
  }
}
const rootReducer = Redux.combineReducers({
  search: searchReducer,
  results: resultsReducer,
  url: urlReducer
})

// use redux-thunk middleware for async api calls
let middleware = Redux.applyMiddleware(ReduxThunk.default)
// use redux devtools if available
middleware = (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || Redux.compose)(
  middleware
)
// create Redux root store
const rootStore = Redux.createStore(rootReducer, middleware)

// render app
ReactDOM.render(
  <Provider store={rootStore}><App /></Provider>,
  document.getElementById("Root")
)
              
            
!
999px

Console